mirror of
https://github.com/bluenviron/mediamtx.git
synced 2025-12-20 02:00:05 -08:00
move RTSP tests into internal/servers/rtsp (#3049)
This commit is contained in:
parent
e8b19b82d5
commit
c7bdcea741
22 changed files with 685 additions and 664 deletions
|
|
@ -18,14 +18,18 @@ var (
|
||||||
|
|
||||||
const plainCredentialSupportedChars = "A-Z,0-9,!,$,(,),*,+,.,;,<,=,>,[,],^,_,-,\",\",@,#,&"
|
const plainCredentialSupportedChars = "A-Z,0-9,!,$,(,),*,+,.,;,<,=,>,[,],^,_,-,\",\",@,#,&"
|
||||||
|
|
||||||
// Credential is a parameter that is used as username or password.
|
func sha256Base64(in string) string {
|
||||||
type Credential struct {
|
h := sha256.New()
|
||||||
value string
|
h.Write([]byte(in))
|
||||||
|
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Credential is a parameter that is used as username or password.
|
||||||
|
type Credential string
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler.
|
// MarshalJSON implements json.Marshaler.
|
||||||
func (d Credential) MarshalJSON() ([]byte, error) {
|
func (d Credential) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(d.value)
|
return json.Marshal(string(d))
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler.
|
// UnmarshalJSON implements json.Unmarshaler.
|
||||||
|
|
@ -35,9 +39,7 @@ func (d *Credential) UnmarshalJSON(b []byte) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
*d = Credential{
|
*d = Credential(in)
|
||||||
value: in,
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.validate()
|
return d.validate()
|
||||||
}
|
}
|
||||||
|
|
@ -47,72 +49,57 @@ func (d *Credential) UnmarshalEnv(_ string, v string) error {
|
||||||
return d.UnmarshalJSON([]byte(`"` + v + `"`))
|
return d.UnmarshalJSON([]byte(`"` + v + `"`))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValue returns the value of the credential.
|
|
||||||
func (d *Credential) GetValue() string {
|
|
||||||
return d.value
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty returns true if the credential is not configured.
|
|
||||||
func (d *Credential) IsEmpty() bool {
|
|
||||||
return d.value == ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSha256 returns true if the credential is a sha256 hash.
|
// IsSha256 returns true if the credential is a sha256 hash.
|
||||||
func (d *Credential) IsSha256() bool {
|
func (d Credential) IsSha256() bool {
|
||||||
return d.value != "" && strings.HasPrefix(d.value, "sha256:")
|
return strings.HasPrefix(string(d), "sha256:")
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsArgon2 returns true if the credential is an argon2 hash.
|
// IsArgon2 returns true if the credential is an argon2 hash.
|
||||||
func (d *Credential) IsArgon2() bool {
|
func (d Credential) IsArgon2() bool {
|
||||||
return d.value != "" && strings.HasPrefix(d.value, "argon2:")
|
return strings.HasPrefix(string(d), "argon2:")
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsHashed returns true if the credential is a sha256 or argon2 hash.
|
// IsHashed returns true if the credential is a sha256 or argon2 hash.
|
||||||
func (d *Credential) IsHashed() bool {
|
func (d Credential) IsHashed() bool {
|
||||||
return d.IsSha256() || d.IsArgon2()
|
return d.IsSha256() || d.IsArgon2()
|
||||||
}
|
}
|
||||||
|
|
||||||
func sha256Base64(in string) string {
|
|
||||||
h := sha256.New()
|
|
||||||
h.Write([]byte(in))
|
|
||||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check returns true if the given value matches the credential.
|
// Check returns true if the given value matches the credential.
|
||||||
func (d *Credential) Check(guess string) bool {
|
func (d Credential) Check(guess string) bool {
|
||||||
if d.IsSha256() {
|
if d.IsSha256() {
|
||||||
return d.value[len("sha256:"):] == sha256Base64(guess)
|
return string(d)[len("sha256:"):] == sha256Base64(guess)
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.IsArgon2() {
|
if d.IsArgon2() {
|
||||||
// TODO: remove matthewhartstonge/argon2 when this PR gets merged into mainline Go:
|
// TODO: remove matthewhartstonge/argon2 when this PR gets merged into mainline Go:
|
||||||
// https://go-review.googlesource.com/c/crypto/+/502515
|
// https://go-review.googlesource.com/c/crypto/+/502515
|
||||||
ok, err := argon2.VerifyEncoded([]byte(guess), []byte(d.value[len("argon2:"):]))
|
ok, err := argon2.VerifyEncoded([]byte(guess), []byte(string(d)[len("argon2:"):]))
|
||||||
return ok && err == nil
|
return ok && err == nil
|
||||||
}
|
}
|
||||||
if d.IsEmpty() {
|
|
||||||
// when no credential is set, any value is valid
|
if d != "" {
|
||||||
return true
|
return string(d) == guess
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.value == guess
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Credential) validate() error {
|
func (d Credential) validate() error {
|
||||||
if !d.IsEmpty() {
|
if d != "" {
|
||||||
switch {
|
switch {
|
||||||
case d.IsSha256():
|
case d.IsSha256():
|
||||||
if !reBase64.MatchString(d.value) {
|
if !reBase64.MatchString(string(d)) {
|
||||||
return fmt.Errorf("credential contains unsupported characters, sha256 hash must be base64 encoded")
|
return fmt.Errorf("credential contains unsupported characters, sha256 hash must be base64 encoded")
|
||||||
}
|
}
|
||||||
case d.IsArgon2():
|
case d.IsArgon2():
|
||||||
// TODO: remove matthewhartstonge/argon2 when this PR gets merged into mainline Go:
|
// TODO: remove matthewhartstonge/argon2 when this PR gets merged into mainline Go:
|
||||||
// https://go-review.googlesource.com/c/crypto/+/502515
|
// https://go-review.googlesource.com/c/crypto/+/502515
|
||||||
_, err := argon2.Decode([]byte(d.value[len("argon2:"):]))
|
_, err := argon2.Decode([]byte(string(d)[len("argon2:"):]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid argon2 hash: %w", err)
|
return fmt.Errorf("invalid argon2 hash: %w", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if !rePlainCredential.MatchString(d.value) {
|
if !rePlainCredential.MatchString(string(d)) {
|
||||||
return fmt.Errorf("credential contains unsupported characters. Supported are: %s", plainCredentialSupportedChars)
|
return fmt.Errorf("credential contains unsupported characters. Supported are: %s", plainCredentialSupportedChars)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
|
|
||||||
func TestCredential(t *testing.T) {
|
func TestCredential(t *testing.T) {
|
||||||
t.Run("MarshalJSON", func(t *testing.T) {
|
t.Run("MarshalJSON", func(t *testing.T) {
|
||||||
cred := Credential{value: "password"}
|
cred := Credential("password")
|
||||||
expectedJSON := []byte(`"password"`)
|
expectedJSON := []byte(`"password"`)
|
||||||
actualJSON, err := cred.MarshalJSON()
|
actualJSON, err := cred.MarshalJSON()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
@ -16,7 +16,7 @@ func TestCredential(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("UnmarshalJSON", func(t *testing.T) {
|
t.Run("UnmarshalJSON", func(t *testing.T) {
|
||||||
expectedCred := Credential{value: "password"}
|
expectedCred := Credential("password")
|
||||||
jsonData := []byte(`"password"`)
|
jsonData := []byte(`"password"`)
|
||||||
var actualCred Credential
|
var actualCred Credential
|
||||||
err := actualCred.UnmarshalJSON(jsonData)
|
err := actualCred.UnmarshalJSON(jsonData)
|
||||||
|
|
@ -25,79 +25,63 @@ func TestCredential(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("UnmarshalEnv", func(t *testing.T) {
|
t.Run("UnmarshalEnv", func(t *testing.T) {
|
||||||
cred := Credential{}
|
cred := Credential("")
|
||||||
err := cred.UnmarshalEnv("", "password")
|
err := cred.UnmarshalEnv("", "password")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "password", cred.value)
|
assert.Equal(t, Credential("password"), cred)
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetValue", func(t *testing.T) {
|
|
||||||
cred := Credential{value: "password"}
|
|
||||||
actualValue := cred.GetValue()
|
|
||||||
assert.Equal(t, "password", actualValue)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("IsEmpty", func(t *testing.T) {
|
|
||||||
cred := Credential{}
|
|
||||||
assert.True(t, cred.IsEmpty())
|
|
||||||
assert.False(t, cred.IsHashed())
|
|
||||||
|
|
||||||
cred.value = "password"
|
|
||||||
assert.False(t, cred.IsEmpty())
|
|
||||||
assert.False(t, cred.IsHashed())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("IsSha256", func(t *testing.T) {
|
t.Run("IsSha256", func(t *testing.T) {
|
||||||
cred := Credential{}
|
cred := Credential("")
|
||||||
assert.False(t, cred.IsSha256())
|
assert.False(t, cred.IsSha256())
|
||||||
assert.False(t, cred.IsHashed())
|
assert.False(t, cred.IsHashed())
|
||||||
|
|
||||||
cred.value = "sha256:j1tsRqDEw9xvq/D7/9tMx6Jh/jMhk3UfjwIB2f1zgMo="
|
cred = "sha256:j1tsRqDEw9xvq/D7/9tMx6Jh/jMhk3UfjwIB2f1zgMo="
|
||||||
assert.True(t, cred.IsSha256())
|
assert.True(t, cred.IsSha256())
|
||||||
assert.True(t, cred.IsHashed())
|
assert.True(t, cred.IsHashed())
|
||||||
|
|
||||||
cred.value = "argon2:$argon2id$v=19$m=65536,t=1," +
|
cred = "argon2:$argon2id$v=19$m=65536,t=1," +
|
||||||
"p=4$WXJGqwIB2qd+pRmxMOw9Dg$X4gvR0ZB2DtQoN8vOnJPR2SeFdUhH9TyVzfV98sfWeE"
|
"p=4$WXJGqwIB2qd+pRmxMOw9Dg$X4gvR0ZB2DtQoN8vOnJPR2SeFdUhH9TyVzfV98sfWeE"
|
||||||
assert.False(t, cred.IsSha256())
|
assert.False(t, cred.IsSha256())
|
||||||
assert.True(t, cred.IsHashed())
|
assert.True(t, cred.IsHashed())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("IsArgon2", func(t *testing.T) {
|
t.Run("IsArgon2", func(t *testing.T) {
|
||||||
cred := Credential{}
|
cred := Credential("")
|
||||||
assert.False(t, cred.IsArgon2())
|
assert.False(t, cred.IsArgon2())
|
||||||
assert.False(t, cred.IsHashed())
|
assert.False(t, cred.IsHashed())
|
||||||
|
|
||||||
cred.value = "sha256:j1tsRqDEw9xvq/D7/9tMx6Jh/jMhk3UfjwIB2f1zgMo="
|
cred = "sha256:j1tsRqDEw9xvq/D7/9tMx6Jh/jMhk3UfjwIB2f1zgMo="
|
||||||
assert.False(t, cred.IsArgon2())
|
assert.False(t, cred.IsArgon2())
|
||||||
assert.True(t, cred.IsHashed())
|
assert.True(t, cred.IsHashed())
|
||||||
|
|
||||||
cred.value = "argon2:$argon2id$v=19$m=65536,t=1," +
|
cred = "argon2:$argon2id$v=19$m=65536,t=1," +
|
||||||
"p=4$WXJGqwIB2qd+pRmxMOw9Dg$X4gvR0ZB2DtQoN8vOnJPR2SeFdUhH9TyVzfV98sfWeE"
|
"p=4$WXJGqwIB2qd+pRmxMOw9Dg$X4gvR0ZB2DtQoN8vOnJPR2SeFdUhH9TyVzfV98sfWeE"
|
||||||
assert.True(t, cred.IsArgon2())
|
assert.True(t, cred.IsArgon2())
|
||||||
assert.True(t, cred.IsHashed())
|
assert.True(t, cred.IsHashed())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Check-plain", func(t *testing.T) {
|
t.Run("Check-plain", func(t *testing.T) {
|
||||||
cred := Credential{value: "password"}
|
cred := Credential("password")
|
||||||
assert.True(t, cred.Check("password"))
|
assert.True(t, cred.Check("password"))
|
||||||
assert.False(t, cred.Check("wrongpassword"))
|
assert.False(t, cred.Check("wrongpassword"))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Check-sha256", func(t *testing.T) {
|
t.Run("Check-sha256", func(t *testing.T) {
|
||||||
cred := Credential{value: "password"}
|
cred := Credential("password")
|
||||||
assert.True(t, cred.Check("password"))
|
assert.True(t, cred.Check("password"))
|
||||||
assert.False(t, cred.Check("wrongpassword"))
|
assert.False(t, cred.Check("wrongpassword"))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Check-sha256", func(t *testing.T) {
|
t.Run("Check-sha256", func(t *testing.T) {
|
||||||
cred := Credential{value: "sha256:rl3rgi4NcZkpAEcacZnQ2VuOfJ0FxAqCRaKB/SwdZoQ="}
|
cred := Credential("sha256:rl3rgi4NcZkpAEcacZnQ2VuOfJ0FxAqCRaKB/SwdZoQ=")
|
||||||
assert.True(t, cred.Check("testuser"))
|
assert.True(t, cred.Check("testuser"))
|
||||||
assert.False(t, cred.Check("notestuser"))
|
assert.False(t, cred.Check("notestuser"))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Check-argon2", func(t *testing.T) {
|
t.Run("Check-argon2", func(t *testing.T) {
|
||||||
cred := Credential{value: "argon2:$argon2id$v=19$m=4096,t=3," +
|
cred := Credential("argon2:$argon2id$v=19$m=4096,t=3," +
|
||||||
"p=1$MTIzNDU2Nzg$Ux/LWeTgJQPyfMMJo1myR64+o8rALHoPmlE1i/TR+58"}
|
"p=1$MTIzNDU2Nzg$Ux/LWeTgJQPyfMMJo1myR64+o8rALHoPmlE1i/TR+58")
|
||||||
assert.True(t, cred.Check("testuser"))
|
assert.True(t, cred.Check("testuser"))
|
||||||
assert.False(t, cred.Check("notestuser"))
|
assert.False(t, cred.Check("notestuser"))
|
||||||
})
|
})
|
||||||
|
|
@ -105,50 +89,50 @@ func TestCredential(t *testing.T) {
|
||||||
t.Run("validate", func(t *testing.T) {
|
t.Run("validate", func(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
cred *Credential
|
cred Credential
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Empty credential",
|
name: "Empty credential",
|
||||||
cred: &Credential{value: ""},
|
cred: Credential(""),
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Valid plain credential",
|
name: "Valid plain credential",
|
||||||
cred: &Credential{value: "validPlain123"},
|
cred: Credential("validPlain123"),
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid plain credential",
|
name: "Invalid plain credential",
|
||||||
cred: &Credential{value: "invalid/Plain"},
|
cred: Credential("invalid/Plain"),
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Valid sha256 credential",
|
name: "Valid sha256 credential",
|
||||||
cred: &Credential{value: "sha256:validBase64EncodedHash=="},
|
cred: Credential("sha256:validBase64EncodedHash=="),
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid sha256 credential",
|
name: "Invalid sha256 credential",
|
||||||
cred: &Credential{value: "sha256:inval*idBase64"},
|
cred: Credential("sha256:inval*idBase64"),
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Valid Argon2 credential",
|
name: "Valid Argon2 credential",
|
||||||
cred: &Credential{value: "argon2:$argon2id$v=19$m=4096," +
|
cred: Credential("argon2:$argon2id$v=19$m=4096," +
|
||||||
"t=3,p=1$MTIzNDU2Nzg$zarsL19s86GzUWlAkvwt4gJBFuU/A9CVuCjNI4fksow"},
|
"t=3,p=1$MTIzNDU2Nzg$zarsL19s86GzUWlAkvwt4gJBFuU/A9CVuCjNI4fksow"),
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid Argon2 credential",
|
name: "Invalid Argon2 credential",
|
||||||
cred: &Credential{value: "argon2:invalid"},
|
cred: Credential("argon2:invalid"),
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid Argon2 credential",
|
name: "Invalid Argon2 credential",
|
||||||
// testing argon2d errors, because it's not supported
|
// testing argon2d errors, because it's not supported
|
||||||
cred: &Credential{value: "$argon2d$v=19$m=4096,t=3," +
|
cred: Credential("$argon2d$v=19$m=4096,t=3," +
|
||||||
"p=1$MTIzNDU2Nzg$Xqyd4R7LzXvvAEHaVU12+Nzf5OkHoYcwIEIIYJUDpz0"},
|
"p=1$MTIzNDU2Nzg$Xqyd4R7LzXvvAEHaVU12+Nzf5OkHoYcwIEIIYJUDpz0"),
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -377,11 +377,11 @@ func (pconf *Path) validate(conf *Conf, name string) error {
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
|
|
||||||
if (!pconf.PublishUser.IsEmpty() && pconf.PublishPass.IsEmpty()) ||
|
if (pconf.PublishUser != "" && pconf.PublishPass == "") ||
|
||||||
(pconf.PublishUser.IsEmpty() && !pconf.PublishPass.IsEmpty()) {
|
(pconf.PublishUser == "" && pconf.PublishPass != "") {
|
||||||
return fmt.Errorf("read username and password must be both filled")
|
return fmt.Errorf("read username and password must be both filled")
|
||||||
}
|
}
|
||||||
if !pconf.PublishUser.IsEmpty() && pconf.Source != "publisher" {
|
if pconf.PublishUser != "" && pconf.Source != "publisher" {
|
||||||
return fmt.Errorf("'publishUser' is useless when source is not 'publisher', since " +
|
return fmt.Errorf("'publishUser' is useless when source is not 'publisher', since " +
|
||||||
"the stream is not provided by a publisher, but by a fixed source")
|
"the stream is not provided by a publisher, but by a fixed source")
|
||||||
}
|
}
|
||||||
|
|
@ -389,8 +389,8 @@ func (pconf *Path) validate(conf *Conf, name string) error {
|
||||||
return fmt.Errorf("'publishIPs' is useless when source is not 'publisher', since " +
|
return fmt.Errorf("'publishIPs' is useless when source is not 'publisher', since " +
|
||||||
"the stream is not provided by a publisher, but by a fixed source")
|
"the stream is not provided by a publisher, but by a fixed source")
|
||||||
}
|
}
|
||||||
if (!pconf.ReadUser.IsEmpty() && pconf.ReadPass.IsEmpty()) ||
|
if (pconf.ReadUser != "" && pconf.ReadPass == "") ||
|
||||||
(pconf.ReadUser.IsEmpty() && !pconf.ReadPass.IsEmpty()) {
|
(pconf.ReadUser == "" && pconf.ReadPass != "") {
|
||||||
return fmt.Errorf("read username and password must be both filled")
|
return fmt.Errorf("read username and password must be both filled")
|
||||||
}
|
}
|
||||||
if contains(conf.AuthMethods, headers.AuthDigest) {
|
if contains(conf.AuthMethods, headers.AuthDigest) {
|
||||||
|
|
@ -402,9 +402,9 @@ func (pconf *Path) validate(conf *Conf, name string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if conf.ExternalAuthenticationURL != "" {
|
if conf.ExternalAuthenticationURL != "" {
|
||||||
if !pconf.PublishUser.IsEmpty() ||
|
if pconf.PublishUser != "" ||
|
||||||
len(pconf.PublishIPs) > 0 ||
|
len(pconf.PublishIPs) > 0 ||
|
||||||
!pconf.ReadUser.IsEmpty() ||
|
pconf.ReadUser != "" ||
|
||||||
len(pconf.ReadIPs) > 0 {
|
len(pconf.ReadIPs) > 0 {
|
||||||
return fmt.Errorf("credentials or IPs can't be used together with 'externalAuthenticationURL'")
|
return fmt.Errorf("credentials or IPs can't be used together with 'externalAuthenticationURL'")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import (
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
|
||||||
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
||||||
srt "github.com/datarhei/gosrt"
|
srt "github.com/datarhei/gosrt"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -29,16 +28,6 @@ import (
|
||||||
"github.com/bluenviron/mediamtx/internal/test"
|
"github.com/bluenviron/mediamtx/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testMediaH264 = &description.Media{
|
|
||||||
Type: description.MediaTypeVideo,
|
|
||||||
Formats: []format.Format{test.FormatH264},
|
|
||||||
}
|
|
||||||
|
|
||||||
var testMediaAAC = &description.Media{
|
|
||||||
Type: description.MediaTypeAudio,
|
|
||||||
Formats: []format.Format{test.FormatMPEG4Audio},
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkClose(t *testing.T, closeFunc func() error) {
|
func checkClose(t *testing.T, closeFunc func() error) {
|
||||||
require.NoError(t, closeFunc())
|
require.NoError(t, closeFunc())
|
||||||
}
|
}
|
||||||
|
|
@ -110,14 +99,14 @@ func TestAPIPathsList(t *testing.T) {
|
||||||
|
|
||||||
hc := &http.Client{Transport: &http.Transport{}}
|
hc := &http.Client{Transport: &http.Transport{}}
|
||||||
|
|
||||||
media0 := testMediaH264
|
media0 := test.UniqueMediaH264()
|
||||||
|
|
||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
err := source.StartRecording(
|
err := source.StartRecording(
|
||||||
"rtsp://localhost:8554/mypath",
|
"rtsp://localhost:8554/mypath",
|
||||||
&description.Session{Medias: []*description.Media{
|
&description.Session{Medias: []*description.Media{
|
||||||
media0,
|
media0,
|
||||||
testMediaAAC,
|
test.MediaMPEG4Audio,
|
||||||
}})
|
}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
@ -171,8 +160,8 @@ func TestAPIPathsList(t *testing.T) {
|
||||||
source := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}}
|
source := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}}
|
||||||
err = source.StartRecording("rtsps://localhost:8322/mypath",
|
err = source.StartRecording("rtsps://localhost:8322/mypath",
|
||||||
&description.Session{Medias: []*description.Media{
|
&description.Session{Medias: []*description.Media{
|
||||||
testMediaH264,
|
test.UniqueMediaH264(),
|
||||||
testMediaAAC,
|
test.UniqueMediaMPEG4Audio(),
|
||||||
}})
|
}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
@ -313,7 +302,7 @@ func TestAPIPathsGet(t *testing.T) {
|
||||||
if ca == "ok" || ca == "ok-nested" {
|
if ca == "ok" || ca == "ok-nested" {
|
||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
err := source.StartRecording("rtsp://localhost:8554/"+pathName,
|
err := source.StartRecording("rtsp://localhost:8554/"+pathName,
|
||||||
&description.Session{Medias: []*description.Media{testMediaH264}})
|
&description.Session{Medias: []*description.Media{test.UniqueMediaH264()}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
|
|
@ -384,7 +373,7 @@ func TestAPIProtocolListGet(t *testing.T) {
|
||||||
|
|
||||||
hc := &http.Client{Transport: &http.Transport{}}
|
hc := &http.Client{Transport: &http.Transport{}}
|
||||||
|
|
||||||
medi := testMediaH264
|
medi := test.UniqueMediaH264()
|
||||||
|
|
||||||
switch ca { //nolint:dupl
|
switch ca { //nolint:dupl
|
||||||
case "rtsp conns", "rtsp sessions":
|
case "rtsp conns", "rtsp sessions":
|
||||||
|
|
@ -947,7 +936,7 @@ func TestAPIProtocolKick(t *testing.T) {
|
||||||
|
|
||||||
hc := &http.Client{Transport: &http.Transport{}}
|
hc := &http.Client{Transport: &http.Transport{}}
|
||||||
|
|
||||||
medi := testMediaH264
|
medi := test.MediaH264
|
||||||
|
|
||||||
switch ca {
|
switch ca {
|
||||||
case "rtsp":
|
case "rtsp":
|
||||||
|
|
|
||||||
|
|
@ -104,12 +104,12 @@ func doAuthentication(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !pathUser.IsEmpty() {
|
if pathUser != "" {
|
||||||
if accessRequest.RTSPRequest != nil && rtspAuth.Method == headers.AuthDigest {
|
if accessRequest.RTSPRequest != nil && rtspAuth.Method == headers.AuthDigest {
|
||||||
err := auth.Validate(
|
err := auth.Validate(
|
||||||
accessRequest.RTSPRequest,
|
accessRequest.RTSPRequest,
|
||||||
pathUser.GetValue(),
|
string(pathUser),
|
||||||
pathPass.GetValue(),
|
string(pathPass),
|
||||||
accessRequest.RTSPBaseURL,
|
accessRequest.RTSPBaseURL,
|
||||||
rtspAuthMethods,
|
rtspAuthMethods,
|
||||||
"IPCAM",
|
"IPCAM",
|
||||||
|
|
|
||||||
155
internal/core/auth_test.go
Normal file
155
internal/core/auth_test.go
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
||||||
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
|
"github.com/bluenviron/mediamtx/internal/defs"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testHTTPAuthenticator struct {
|
||||||
|
*http.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *testHTTPAuthenticator) initialize(t *testing.T, protocol string, action string) {
|
||||||
|
firstReceived := false
|
||||||
|
|
||||||
|
ts.Server = &http.Server{
|
||||||
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
require.Equal(t, http.MethodPost, r.Method)
|
||||||
|
require.Equal(t, "/auth", r.URL.Path)
|
||||||
|
|
||||||
|
var in struct {
|
||||||
|
IP string `json:"ip"`
|
||||||
|
User string `json:"user"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
Query string `json:"query"`
|
||||||
|
}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&in)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var user string
|
||||||
|
if action == "publish" {
|
||||||
|
user = "testpublisher"
|
||||||
|
} else {
|
||||||
|
user = "testreader"
|
||||||
|
}
|
||||||
|
|
||||||
|
if in.IP != "127.0.0.1" ||
|
||||||
|
in.User != user ||
|
||||||
|
in.Password != "testpass" ||
|
||||||
|
in.Path != "teststream" ||
|
||||||
|
in.Protocol != protocol ||
|
||||||
|
(firstReceived && in.ID == "") ||
|
||||||
|
in.Action != action ||
|
||||||
|
(in.Query != "user=testreader&pass=testpass¶m=value" &&
|
||||||
|
in.Query != "user=testpublisher&pass=testpass¶m=value" &&
|
||||||
|
in.Query != "param=value") {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
firstReceived = true
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
ln, err := net.Listen("tcp", "127.0.0.1:9120")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
go ts.Server.Serve(ln)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *testHTTPAuthenticator) close() {
|
||||||
|
ts.Server.Shutdown(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthSha256(t *testing.T) {
|
||||||
|
err := doAuthentication(
|
||||||
|
"",
|
||||||
|
conf.AuthMethods{headers.AuthBasic},
|
||||||
|
&conf.Path{
|
||||||
|
PublishUser: conf.Credential("sha256:rl3rgi4NcZkpAEcacZnQ2VuOfJ0FxAqCRaKB/SwdZoQ="),
|
||||||
|
PublishPass: conf.Credential("sha256:E9JJ8stBJ7QM+nV4ZoUCeHk/gU3tPFh/5YieiJp6n2w="),
|
||||||
|
},
|
||||||
|
defs.PathAccessRequest{
|
||||||
|
Name: "mypath",
|
||||||
|
Query: "",
|
||||||
|
Publish: true,
|
||||||
|
SkipAuth: false,
|
||||||
|
IP: net.ParseIP("127.0.0.1"),
|
||||||
|
User: "testuser",
|
||||||
|
Pass: "testpass",
|
||||||
|
Proto: defs.AuthProtocolRTSP,
|
||||||
|
ID: nil,
|
||||||
|
RTSPRequest: nil,
|
||||||
|
RTSPBaseURL: nil,
|
||||||
|
RTSPNonce: "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthArgon2(t *testing.T) {
|
||||||
|
err := doAuthentication(
|
||||||
|
"",
|
||||||
|
conf.AuthMethods{headers.AuthBasic},
|
||||||
|
&conf.Path{
|
||||||
|
PublishUser: conf.Credential(
|
||||||
|
"argon2:$argon2id$v=19$m=4096,t=3,p=1$MTIzNDU2Nzg$Ux/LWeTgJQPyfMMJo1myR64+o8rALHoPmlE1i/TR+58"),
|
||||||
|
PublishPass: conf.Credential(
|
||||||
|
"argon2:$argon2i$v=19$m=4096,t=3,p=1$MTIzNDU2Nzg$/mrZ42TiTv1mcPnpMUera5oi0SFYbbyueAbdx5sUvWo"),
|
||||||
|
},
|
||||||
|
defs.PathAccessRequest{
|
||||||
|
Name: "mypath",
|
||||||
|
Query: "",
|
||||||
|
Publish: true,
|
||||||
|
SkipAuth: false,
|
||||||
|
IP: net.ParseIP("127.0.0.1"),
|
||||||
|
User: "testuser",
|
||||||
|
Pass: "testpass",
|
||||||
|
Proto: defs.AuthProtocolRTSP,
|
||||||
|
ID: nil,
|
||||||
|
RTSPRequest: nil,
|
||||||
|
RTSPBaseURL: nil,
|
||||||
|
RTSPNonce: "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthExternal(t *testing.T) {
|
||||||
|
au := &testHTTPAuthenticator{}
|
||||||
|
au.initialize(t, "rtsp", "publish")
|
||||||
|
defer au.close()
|
||||||
|
|
||||||
|
err := doAuthentication(
|
||||||
|
"http://127.0.0.1:9120/auth",
|
||||||
|
conf.AuthMethods{headers.AuthBasic},
|
||||||
|
&conf.Path{},
|
||||||
|
defs.PathAccessRequest{
|
||||||
|
Name: "teststream",
|
||||||
|
Query: "param=value",
|
||||||
|
Publish: true,
|
||||||
|
SkipAuth: false,
|
||||||
|
IP: net.ParseIP("127.0.0.1"),
|
||||||
|
User: "testpublisher",
|
||||||
|
Pass: "testpass",
|
||||||
|
Proto: defs.AuthProtocolRTSP,
|
||||||
|
ID: nil,
|
||||||
|
RTSPRequest: nil,
|
||||||
|
RTSPBaseURL: nil,
|
||||||
|
RTSPNonce: "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
@ -110,11 +110,9 @@ func TestCoreHotReloading(t *testing.T) {
|
||||||
defer p.Close()
|
defer p.Close()
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
medi := testMediaH264
|
|
||||||
|
|
||||||
c := gortsplib.Client{}
|
c := gortsplib.Client{}
|
||||||
err = c.StartRecording("rtsp://localhost:8554/test1",
|
err = c.StartRecording("rtsp://localhost:8554/test1",
|
||||||
&description.Session{Medias: []*description.Media{medi}})
|
&description.Session{Medias: []*description.Media{test.UniqueMediaH264()}})
|
||||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -126,11 +124,9 @@ func TestCoreHotReloading(t *testing.T) {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
medi := testMediaH264
|
|
||||||
|
|
||||||
conn := gortsplib.Client{}
|
conn := gortsplib.Client{}
|
||||||
err = conn.StartRecording("rtsp://localhost:8554/test1",
|
err = conn.StartRecording("rtsp://localhost:8554/test1",
|
||||||
&description.Session{Medias: []*description.Media{medi}})
|
&description.Session{Medias: []*description.Media{test.UniqueMediaH264()}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
}()
|
}()
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import (
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
|
||||||
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
||||||
srt "github.com/datarhei/gosrt"
|
srt "github.com/datarhei/gosrt"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
@ -110,10 +109,7 @@ webrtc_sessions_bytes_sent 0
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
err := source.StartRecording("rtsp://localhost:8554/rtsp_path",
|
err := source.StartRecording("rtsp://localhost:8554/rtsp_path",
|
||||||
&description.Session{Medias: []*description.Media{{
|
&description.Session{Medias: []*description.Media{test.UniqueMediaH264()}})
|
||||||
Type: description.MediaTypeVideo,
|
|
||||||
Formats: []format.Format{test.FormatH264},
|
|
||||||
}}})
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
<-terminate
|
<-terminate
|
||||||
|
|
@ -123,10 +119,7 @@ webrtc_sessions_bytes_sent 0
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
source2 := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}}
|
source2 := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}}
|
||||||
err := source2.StartRecording("rtsps://localhost:8322/rtsps_path",
|
err := source2.StartRecording("rtsps://localhost:8322/rtsps_path",
|
||||||
&description.Session{Medias: []*description.Media{{
|
&description.Session{Medias: []*description.Media{test.UniqueMediaH264()}})
|
||||||
Type: description.MediaTypeVideo,
|
|
||||||
Formats: []format.Format{test.FormatH264},
|
|
||||||
}}})
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source2.Close()
|
defer source2.Close()
|
||||||
<-terminate
|
<-terminate
|
||||||
|
|
@ -178,7 +171,7 @@ webrtc_sessions_bytes_sent 0
|
||||||
Log: test.NilLogger{},
|
Log: test.NilLogger{},
|
||||||
}
|
}
|
||||||
|
|
||||||
tracks, err := s.Publish(context.Background(), testMediaH264.Formats[0], nil)
|
tracks, err := s.Publish(context.Background(), test.MediaH264.Formats[0], nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer checkClose(t, s.Close)
|
defer checkClose(t, s.Close)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,9 @@ import (
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
||||||
"github.com/bluenviron/mediamtx/internal/defs"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ defs.PathManager = &pathManager{}
|
|
||||||
|
|
||||||
func TestPathAutoDeletion(t *testing.T) {
|
func TestPathAutoDeletion(t *testing.T) {
|
||||||
for _, ca := range []string{"describe", "setup"} {
|
for _, ca := range []string{"describe", "setup"} {
|
||||||
t.Run(ca, func(t *testing.T) {
|
t.Run(ca, func(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,7 @@ func TestPathRunOnConnect(t *testing.T) {
|
||||||
|
|
||||||
err := c.StartRecording(
|
err := c.StartRecording(
|
||||||
"rtsp://localhost:8554/test",
|
"rtsp://localhost:8554/test",
|
||||||
&description.Session{Medias: []*description.Media{testMediaH264}})
|
&description.Session{Medias: []*description.Media{test.UniqueMediaH264()}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
|
|
@ -302,9 +302,10 @@ func TestPathRunOnReady(t *testing.T) {
|
||||||
defer p.Close()
|
defer p.Close()
|
||||||
|
|
||||||
c := gortsplib.Client{}
|
c := gortsplib.Client{}
|
||||||
|
|
||||||
err := c.StartRecording(
|
err := c.StartRecording(
|
||||||
"rtsp://localhost:8554/test?query=value",
|
"rtsp://localhost:8554/test?query=value",
|
||||||
&description.Session{Medias: []*description.Media{testMediaH264}})
|
&description.Session{Medias: []*description.Media{test.UniqueMediaH264()}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
|
|
@ -339,10 +340,13 @@ func TestPathRunOnRead(t *testing.T) {
|
||||||
require.Equal(t, true, ok)
|
require.Equal(t, true, ok)
|
||||||
defer p.Close()
|
defer p.Close()
|
||||||
|
|
||||||
|
media0 := test.UniqueMediaH264()
|
||||||
|
|
||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
|
|
||||||
err := source.StartRecording(
|
err := source.StartRecording(
|
||||||
"rtsp://localhost:8554/test",
|
"rtsp://localhost:8554/test",
|
||||||
&description.Session{Medias: []*description.Media{testMediaH264}})
|
&description.Session{Medias: []*description.Media{media0}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
|
|
@ -419,7 +423,7 @@ func TestPathRunOnRead(t *testing.T) {
|
||||||
case <-writerTerminate:
|
case <-writerTerminate:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := source.WritePacketRTP(testMediaH264, &rtp.Packet{
|
err := source.WritePacketRTP(media0, &rtp.Packet{
|
||||||
Header: rtp.Header{
|
Header: rtp.Header{
|
||||||
Version: 2,
|
Version: 2,
|
||||||
Marker: true,
|
Marker: true,
|
||||||
|
|
@ -462,11 +466,12 @@ func TestPathMaxReaders(t *testing.T) {
|
||||||
defer p.Close()
|
defer p.Close()
|
||||||
|
|
||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
|
|
||||||
err := source.StartRecording(
|
err := source.StartRecording(
|
||||||
"rtsp://localhost:8554/mystream",
|
"rtsp://localhost:8554/mystream",
|
||||||
&description.Session{Medias: []*description.Media{
|
&description.Session{Medias: []*description.Media{
|
||||||
testMediaH264,
|
test.UniqueMediaH264(),
|
||||||
testMediaAAC,
|
test.UniqueMediaMPEG4Audio(),
|
||||||
}})
|
}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
@ -507,15 +512,18 @@ func TestPathRecord(t *testing.T) {
|
||||||
require.Equal(t, true, ok)
|
require.Equal(t, true, ok)
|
||||||
defer p.Close()
|
defer p.Close()
|
||||||
|
|
||||||
|
media0 := test.UniqueMediaH264()
|
||||||
|
|
||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
|
|
||||||
err = source.StartRecording(
|
err = source.StartRecording(
|
||||||
"rtsp://localhost:8554/mystream",
|
"rtsp://localhost:8554/mystream",
|
||||||
&description.Session{Medias: []*description.Media{testMediaH264}})
|
&description.Session{Medias: []*description.Media{media0}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
err := source.WritePacketRTP(testMediaH264, &rtp.Packet{
|
err := source.WritePacketRTP(media0, &rtp.Packet{
|
||||||
Header: rtp.Header{
|
Header: rtp.Header{
|
||||||
Version: 2,
|
Version: 2,
|
||||||
Marker: true,
|
Marker: true,
|
||||||
|
|
@ -550,7 +558,7 @@ func TestPathRecord(t *testing.T) {
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
for i := 4; i < 8; i++ {
|
for i := 4; i < 8; i++ {
|
||||||
err := source.WritePacketRTP(testMediaH264, &rtp.Packet{
|
err := source.WritePacketRTP(media0, &rtp.Packet{
|
||||||
Header: rtp.Header{
|
Header: rtp.Header{
|
||||||
Version: 2,
|
Version: 2,
|
||||||
Marker: true,
|
Marker: true,
|
||||||
|
|
@ -607,7 +615,7 @@ func TestPathFallback(t *testing.T) {
|
||||||
|
|
||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
err := source.StartRecording("rtsp://localhost:8554/path2",
|
err := source.StartRecording("rtsp://localhost:8554/path2",
|
||||||
&description.Session{Medias: []*description.Media{testMediaH264}})
|
&description.Session{Medias: []*description.Media{test.UniqueMediaH264()}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
|
|
@ -656,7 +664,7 @@ func TestPathSourceRegexp(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{testMediaH264}})
|
stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{test.MediaH264}})
|
||||||
defer stream.Close()
|
defer stream.Close()
|
||||||
|
|
||||||
p, ok := newInstance(
|
p, ok := newInstance(
|
||||||
|
|
@ -680,3 +688,106 @@ func TestPathSourceRegexp(t *testing.T) {
|
||||||
_, _, err = reader.Describe(u)
|
_, _, err = reader.Describe(u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPathOverridePublisher(t *testing.T) {
|
||||||
|
for _, ca := range []string{
|
||||||
|
"enabled",
|
||||||
|
"disabled",
|
||||||
|
} {
|
||||||
|
t.Run(ca, func(t *testing.T) {
|
||||||
|
conf := "rtmp: no\n" +
|
||||||
|
"paths:\n" +
|
||||||
|
" all_others:\n"
|
||||||
|
|
||||||
|
if ca == "disabled" {
|
||||||
|
conf += " overridePublisher: no\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
p, ok := newInstance(conf)
|
||||||
|
require.Equal(t, true, ok)
|
||||||
|
defer p.Close()
|
||||||
|
|
||||||
|
medi := test.UniqueMediaH264()
|
||||||
|
|
||||||
|
s1 := gortsplib.Client{}
|
||||||
|
|
||||||
|
err := s1.StartRecording("rtsp://localhost:8554/teststream",
|
||||||
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer s1.Close()
|
||||||
|
|
||||||
|
s2 := gortsplib.Client{}
|
||||||
|
|
||||||
|
err = s2.StartRecording("rtsp://localhost:8554/teststream",
|
||||||
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
|
if ca == "enabled" {
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer s2.Close()
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
frameRecv := make(chan struct{})
|
||||||
|
|
||||||
|
c := gortsplib.Client{}
|
||||||
|
|
||||||
|
u, err := base.ParseURL("rtsp://localhost:8554/teststream")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = c.Start(u.Scheme, u.Host)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
desc, _, err := c.Describe(u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = c.SetupAll(desc.BaseURL, desc.Medias)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
c.OnPacketRTP(desc.Medias[0], desc.Medias[0].Formats[0], func(pkt *rtp.Packet) {
|
||||||
|
if ca == "enabled" {
|
||||||
|
require.Equal(t, []byte{5, 15, 16, 17, 18}, pkt.Payload)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, []byte{5, 11, 12, 13, 14}, pkt.Payload)
|
||||||
|
}
|
||||||
|
close(frameRecv)
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err = c.Play(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if ca == "enabled" {
|
||||||
|
err := s1.Wait()
|
||||||
|
require.EqualError(t, err, "EOF")
|
||||||
|
|
||||||
|
err = s2.WritePacketRTP(medi, &rtp.Packet{
|
||||||
|
Header: rtp.Header{
|
||||||
|
Version: 0x02,
|
||||||
|
PayloadType: 96,
|
||||||
|
SequenceNumber: 57899,
|
||||||
|
Timestamp: 345234345,
|
||||||
|
SSRC: 978651231,
|
||||||
|
Marker: true,
|
||||||
|
},
|
||||||
|
Payload: []byte{5, 15, 16, 17, 18},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
err = s1.WritePacketRTP(medi, &rtp.Packet{
|
||||||
|
Header: rtp.Header{
|
||||||
|
Version: 0x02,
|
||||||
|
PayloadType: 96,
|
||||||
|
SequenceNumber: 57899,
|
||||||
|
Timestamp: 345234345,
|
||||||
|
SSRC: 978651231,
|
||||||
|
Marker: true,
|
||||||
|
},
|
||||||
|
Payload: []byte{5, 11, 12, 13, 14},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-frameRecv
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,441 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4"
|
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
|
||||||
"github.com/pion/rtp"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testHTTPAuthenticator struct {
|
|
||||||
*http.Server
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestHTTPAuthenticator(t *testing.T, protocol string, action string) *testHTTPAuthenticator {
|
|
||||||
firstReceived := false
|
|
||||||
|
|
||||||
ts := &testHTTPAuthenticator{}
|
|
||||||
|
|
||||||
ts.Server = &http.Server{
|
|
||||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
require.Equal(t, http.MethodPost, r.Method)
|
|
||||||
require.Equal(t, "/auth", r.URL.Path)
|
|
||||||
|
|
||||||
var in struct {
|
|
||||||
IP string `json:"ip"`
|
|
||||||
User string `json:"user"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
Protocol string `json:"protocol"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
Action string `json:"action"`
|
|
||||||
Query string `json:"query"`
|
|
||||||
}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&in)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var user string
|
|
||||||
if action == "publish" {
|
|
||||||
user = "testpublisher"
|
|
||||||
} else {
|
|
||||||
user = "testreader"
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.IP != "127.0.0.1" ||
|
|
||||||
in.User != user ||
|
|
||||||
in.Password != "testpass" ||
|
|
||||||
in.Path != "teststream" ||
|
|
||||||
in.Protocol != protocol ||
|
|
||||||
(firstReceived && in.ID == "") ||
|
|
||||||
in.Action != action ||
|
|
||||||
(in.Query != "user=testreader&pass=testpass¶m=value" &&
|
|
||||||
in.Query != "user=testpublisher&pass=testpass¶m=value" &&
|
|
||||||
in.Query != "param=value") {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
firstReceived = true
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
ln, err := net.Listen("tcp", "127.0.0.1:9120")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
go ts.Server.Serve(ln)
|
|
||||||
|
|
||||||
return ts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *testHTTPAuthenticator) close() {
|
|
||||||
ts.Server.Shutdown(context.Background())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRTSPServer(t *testing.T) {
|
|
||||||
for _, auth := range []string{
|
|
||||||
"none",
|
|
||||||
"internal",
|
|
||||||
"external",
|
|
||||||
} {
|
|
||||||
t.Run("auth_"+auth, func(t *testing.T) {
|
|
||||||
var conf string
|
|
||||||
|
|
||||||
switch auth {
|
|
||||||
case "none":
|
|
||||||
conf = "paths:\n" +
|
|
||||||
" all_others:\n"
|
|
||||||
|
|
||||||
case "internal":
|
|
||||||
conf = "rtmp: no\n" +
|
|
||||||
"hls: no\n" +
|
|
||||||
"webrtc: no\n" +
|
|
||||||
"paths:\n" +
|
|
||||||
" all_others:\n" +
|
|
||||||
" publishUser: testpublisher\n" +
|
|
||||||
" publishPass: testpass\n" +
|
|
||||||
" publishIPs: [127.0.0.0/16]\n" +
|
|
||||||
" readUser: testreader\n" +
|
|
||||||
" readPass: testpass\n" +
|
|
||||||
" readIPs: [127.0.0.0/16]\n"
|
|
||||||
|
|
||||||
case "external":
|
|
||||||
conf = "externalAuthenticationURL: http://localhost:9120/auth\n" +
|
|
||||||
"paths:\n" +
|
|
||||||
" all_others:\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
p, ok := newInstance(conf)
|
|
||||||
require.Equal(t, true, ok)
|
|
||||||
defer p.Close()
|
|
||||||
|
|
||||||
var a *testHTTPAuthenticator
|
|
||||||
if auth == "external" {
|
|
||||||
a = newTestHTTPAuthenticator(t, "rtsp", "publish")
|
|
||||||
}
|
|
||||||
|
|
||||||
medi := testMediaH264
|
|
||||||
|
|
||||||
source := gortsplib.Client{}
|
|
||||||
|
|
||||||
err := source.StartRecording(
|
|
||||||
"rtsp://testpublisher:testpass@127.0.0.1:8554/teststream?param=value",
|
|
||||||
&description.Session{Medias: []*description.Media{medi}})
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer source.Close()
|
|
||||||
|
|
||||||
if auth == "external" {
|
|
||||||
a.close()
|
|
||||||
a = newTestHTTPAuthenticator(t, "rtsp", "read")
|
|
||||||
defer a.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := gortsplib.Client{}
|
|
||||||
|
|
||||||
u, err := base.ParseURL("rtsp://testreader:testpass@127.0.0.1:8554/teststream?param=value")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = reader.Start(u.Scheme, u.Host)
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer reader.Close()
|
|
||||||
|
|
||||||
desc, _, err := reader.Describe(u)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = reader.SetupAll(desc.BaseURL, desc.Medias)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = reader.Play(nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRTSPServerAuthHashedSHA256(t *testing.T) {
|
|
||||||
p, ok := newInstance(
|
|
||||||
"rtmp: no\n" +
|
|
||||||
"hls: no\n" +
|
|
||||||
"webrtc: no\n" +
|
|
||||||
"paths:\n" +
|
|
||||||
" all_others:\n" +
|
|
||||||
" publishUser: sha256:rl3rgi4NcZkpAEcacZnQ2VuOfJ0FxAqCRaKB/SwdZoQ=\n" +
|
|
||||||
" publishPass: sha256:E9JJ8stBJ7QM+nV4ZoUCeHk/gU3tPFh/5YieiJp6n2w=\n")
|
|
||||||
require.Equal(t, true, ok)
|
|
||||||
defer p.Close()
|
|
||||||
|
|
||||||
medi := testMediaH264
|
|
||||||
|
|
||||||
source := gortsplib.Client{}
|
|
||||||
|
|
||||||
err := source.StartRecording(
|
|
||||||
"rtsp://testuser:testpass@127.0.0.1:8554/test/stream",
|
|
||||||
&description.Session{Medias: []*description.Media{medi}})
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer source.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRTSPServerAuthHashedArgon2(t *testing.T) {
|
|
||||||
p, ok := newInstance(
|
|
||||||
"rtmp: no\n" +
|
|
||||||
"hls: no\n" +
|
|
||||||
"webrtc: no\n" +
|
|
||||||
"paths:\n" +
|
|
||||||
" all_others:\n" +
|
|
||||||
" publishUser: argon2:$argon2id$v=19$m=4096,t=3,p=1$MTIzNDU2Nzg$Ux/LWeTgJQPyfMMJo1myR64+o8rALHoPmlE1i/TR+58\n" +
|
|
||||||
" publishPass: argon2:$argon2i$v=19$m=4096,t=3,p=1$MTIzNDU2Nzg$/mrZ42TiTv1mcPnpMUera5oi0SFYbbyueAbdx5sUvWo\n")
|
|
||||||
require.Equal(t, true, ok)
|
|
||||||
defer p.Close()
|
|
||||||
|
|
||||||
medi := testMediaH264
|
|
||||||
|
|
||||||
source := gortsplib.Client{}
|
|
||||||
|
|
||||||
err := source.StartRecording(
|
|
||||||
"rtsp://testuser:testpass@127.0.0.1:8554/test/stream",
|
|
||||||
&description.Session{Medias: []*description.Media{medi}})
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer source.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRTSPServerAuthFail(t *testing.T) {
|
|
||||||
for _, ca := range []struct {
|
|
||||||
name string
|
|
||||||
user string
|
|
||||||
pass string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"wronguser",
|
|
||||||
"test1user",
|
|
||||||
"testpass",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"wrongpass",
|
|
||||||
"testuser",
|
|
||||||
"test1pass",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"wrongboth",
|
|
||||||
"test1user",
|
|
||||||
"test1pass",
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run("publish_"+ca.name, func(t *testing.T) {
|
|
||||||
p, ok := newInstance("rtmp: no\n" +
|
|
||||||
"hls: no\n" +
|
|
||||||
"webrtc: no\n" +
|
|
||||||
"paths:\n" +
|
|
||||||
" all_others:\n" +
|
|
||||||
" publishUser: testuser\n" +
|
|
||||||
" publishPass: testpass\n")
|
|
||||||
require.Equal(t, true, ok)
|
|
||||||
defer p.Close()
|
|
||||||
|
|
||||||
medi := testMediaH264
|
|
||||||
|
|
||||||
c := gortsplib.Client{}
|
|
||||||
|
|
||||||
err := c.StartRecording(
|
|
||||||
"rtsp://"+ca.user+":"+ca.pass+"@localhost:8554/test/stream",
|
|
||||||
&description.Session{Medias: []*description.Media{medi}},
|
|
||||||
)
|
|
||||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ca := range []struct {
|
|
||||||
name string
|
|
||||||
user string
|
|
||||||
pass string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"wronguser",
|
|
||||||
"test1user",
|
|
||||||
"testpass",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"wrongpass",
|
|
||||||
"testuser",
|
|
||||||
"test1pass",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"wrongboth",
|
|
||||||
"test1user",
|
|
||||||
"test1pass",
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run("read_"+ca.name, func(t *testing.T) {
|
|
||||||
p, ok := newInstance("rtmp: no\n" +
|
|
||||||
"hls: no\n" +
|
|
||||||
"webrtc: no\n" +
|
|
||||||
"paths:\n" +
|
|
||||||
" all_others:\n" +
|
|
||||||
" readUser: testuser\n" +
|
|
||||||
" readPass: testpass\n")
|
|
||||||
require.Equal(t, true, ok)
|
|
||||||
defer p.Close()
|
|
||||||
|
|
||||||
c := gortsplib.Client{}
|
|
||||||
|
|
||||||
u, err := base.ParseURL("rtsp://" + ca.user + ":" + ca.pass + "@localhost:8554/test/stream")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = c.Start(u.Scheme, u.Host)
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
_, _, err = c.Describe(u)
|
|
||||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("ip", func(t *testing.T) {
|
|
||||||
p, ok := newInstance("rtmp: no\n" +
|
|
||||||
"hls: no\n" +
|
|
||||||
"webrtc: no\n" +
|
|
||||||
"paths:\n" +
|
|
||||||
" all_others:\n" +
|
|
||||||
" publishIPs: [128.0.0.1/32]\n")
|
|
||||||
require.Equal(t, true, ok)
|
|
||||||
defer p.Close()
|
|
||||||
|
|
||||||
medi := testMediaH264
|
|
||||||
|
|
||||||
c := gortsplib.Client{}
|
|
||||||
|
|
||||||
err := c.StartRecording(
|
|
||||||
"rtsp://localhost:8554/test/stream",
|
|
||||||
&description.Session{Medias: []*description.Media{medi}},
|
|
||||||
)
|
|
||||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("external", func(t *testing.T) {
|
|
||||||
p, ok := newInstance("externalAuthenticationURL: http://localhost:9120/auth\n" +
|
|
||||||
"paths:\n" +
|
|
||||||
" all_others:\n")
|
|
||||||
require.Equal(t, true, ok)
|
|
||||||
defer p.Close()
|
|
||||||
|
|
||||||
a := newTestHTTPAuthenticator(t, "rtsp", "publish")
|
|
||||||
defer a.close()
|
|
||||||
|
|
||||||
medi := testMediaH264
|
|
||||||
|
|
||||||
c := gortsplib.Client{}
|
|
||||||
|
|
||||||
err := c.StartRecording(
|
|
||||||
"rtsp://testpublisher2:testpass@localhost:8554/teststream?param=value",
|
|
||||||
&description.Session{Medias: []*description.Media{medi}},
|
|
||||||
)
|
|
||||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRTSPServerPublisherOverride(t *testing.T) {
|
|
||||||
for _, ca := range []string{
|
|
||||||
"enabled",
|
|
||||||
"disabled",
|
|
||||||
} {
|
|
||||||
t.Run(ca, func(t *testing.T) {
|
|
||||||
conf := "rtmp: no\n" +
|
|
||||||
"paths:\n" +
|
|
||||||
" all_others:\n"
|
|
||||||
|
|
||||||
if ca == "disabled" {
|
|
||||||
conf += " overridePublisher: no\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
p, ok := newInstance(conf)
|
|
||||||
require.Equal(t, true, ok)
|
|
||||||
defer p.Close()
|
|
||||||
|
|
||||||
medi := testMediaH264
|
|
||||||
|
|
||||||
s1 := gortsplib.Client{}
|
|
||||||
|
|
||||||
err := s1.StartRecording("rtsp://localhost:8554/teststream",
|
|
||||||
&description.Session{Medias: []*description.Media{medi}})
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer s1.Close()
|
|
||||||
|
|
||||||
s2 := gortsplib.Client{}
|
|
||||||
|
|
||||||
err = s2.StartRecording("rtsp://localhost:8554/teststream",
|
|
||||||
&description.Session{Medias: []*description.Media{medi}})
|
|
||||||
if ca == "enabled" {
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer s2.Close()
|
|
||||||
} else {
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
frameRecv := make(chan struct{})
|
|
||||||
|
|
||||||
c := gortsplib.Client{}
|
|
||||||
|
|
||||||
u, err := base.ParseURL("rtsp://localhost:8554/teststream")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = c.Start(u.Scheme, u.Host)
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
desc, _, err := c.Describe(u)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = c.SetupAll(desc.BaseURL, desc.Medias)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
c.OnPacketRTP(desc.Medias[0], desc.Medias[0].Formats[0], func(pkt *rtp.Packet) {
|
|
||||||
if ca == "enabled" {
|
|
||||||
require.Equal(t, []byte{5, 15, 16, 17, 18}, pkt.Payload)
|
|
||||||
} else {
|
|
||||||
require.Equal(t, []byte{5, 11, 12, 13, 14}, pkt.Payload)
|
|
||||||
}
|
|
||||||
close(frameRecv)
|
|
||||||
})
|
|
||||||
|
|
||||||
_, err = c.Play(nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
if ca == "enabled" {
|
|
||||||
err := s1.Wait()
|
|
||||||
require.EqualError(t, err, "EOF")
|
|
||||||
|
|
||||||
err = s2.WritePacketRTP(medi, &rtp.Packet{
|
|
||||||
Header: rtp.Header{
|
|
||||||
Version: 0x02,
|
|
||||||
PayloadType: 96,
|
|
||||||
SequenceNumber: 57899,
|
|
||||||
Timestamp: 345234345,
|
|
||||||
SSRC: 978651231,
|
|
||||||
Marker: true,
|
|
||||||
},
|
|
||||||
Payload: []byte{5, 15, 16, 17, 18},
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
} else {
|
|
||||||
err = s1.WritePacketRTP(medi, &rtp.Packet{
|
|
||||||
Header: rtp.Header{
|
|
||||||
Version: 0x02,
|
|
||||||
PayloadType: 96,
|
|
||||||
SequenceNumber: 57899,
|
|
||||||
Timestamp: 345234345,
|
|
||||||
SSRC: 978651231,
|
|
||||||
Marker: true,
|
|
||||||
},
|
|
||||||
Payload: []byte{5, 11, 12, 13, 14},
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
<-frameRecv
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package defs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
|
||||||
"github.com/bluenviron/mediamtx/internal/stream"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PathManager is a path manager.
|
|
||||||
type PathManager interface {
|
|
||||||
FindPathConf(req PathFindPathConfReq) (*conf.Path, error)
|
|
||||||
Describe(req PathDescribeReq) PathDescribeRes
|
|
||||||
AddPublisher(req PathAddPublisherReq) (Path, error)
|
|
||||||
AddReader(req PathAddReaderReq) (Path, *stream.Stream, error)
|
|
||||||
}
|
|
||||||
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"github.com/bluenviron/gohlslib"
|
"github.com/bluenviron/gohlslib"
|
||||||
"github.com/bluenviron/gohlslib/pkg/codecs"
|
"github.com/bluenviron/gohlslib/pkg/codecs"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
"github.com/bluenviron/mediamtx/internal/defs"
|
"github.com/bluenviron/mediamtx/internal/defs"
|
||||||
|
|
@ -119,12 +118,7 @@ func TestServerNotFound(t *testing.T) {
|
||||||
|
|
||||||
func TestServerRead(t *testing.T) {
|
func TestServerRead(t *testing.T) {
|
||||||
t.Run("always remux off", func(t *testing.T) {
|
t.Run("always remux off", func(t *testing.T) {
|
||||||
testMediaH264 := &description.Media{
|
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
|
||||||
Type: description.MediaTypeVideo,
|
|
||||||
Formats: []format.Format{test.FormatH264},
|
|
||||||
}
|
|
||||||
|
|
||||||
desc := &description.Session{Medias: []*description.Media{testMediaH264}}
|
|
||||||
|
|
||||||
stream, err := stream.New(
|
stream, err := stream.New(
|
||||||
1460,
|
1460,
|
||||||
|
|
@ -194,7 +188,7 @@ func TestServerRead(t *testing.T) {
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
stream.WriteUnit(testMediaH264, test.FormatH264, &unit.H264{
|
stream.WriteUnit(test.MediaH264, test.FormatH264, &unit.H264{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Time{},
|
NTP: time.Time{},
|
||||||
PTS: time.Duration(i) * time.Second,
|
PTS: time.Duration(i) * time.Second,
|
||||||
|
|
@ -210,12 +204,7 @@ func TestServerRead(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("always remux on", func(t *testing.T) {
|
t.Run("always remux on", func(t *testing.T) {
|
||||||
testMediaH264 := &description.Media{
|
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
|
||||||
Type: description.MediaTypeVideo,
|
|
||||||
Formats: []format.Format{test.FormatH264},
|
|
||||||
}
|
|
||||||
|
|
||||||
desc := &description.Session{Medias: []*description.Media{testMediaH264}}
|
|
||||||
|
|
||||||
stream, err := stream.New(
|
stream, err := stream.New(
|
||||||
1460,
|
1460,
|
||||||
|
|
@ -256,7 +245,7 @@ func TestServerRead(t *testing.T) {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
stream.WriteUnit(testMediaH264, test.FormatH264, &unit.H264{
|
stream.WriteUnit(test.MediaH264, test.FormatH264, &unit.H264{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Time{},
|
NTP: time.Time{},
|
||||||
PTS: time.Duration(i) * time.Second,
|
PTS: time.Duration(i) * time.Second,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
|
||||||
"github.com/bluenviron/mediamtx/internal/asyncwriter"
|
"github.com/bluenviron/mediamtx/internal/asyncwriter"
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
"github.com/bluenviron/mediamtx/internal/defs"
|
"github.com/bluenviron/mediamtx/internal/defs"
|
||||||
|
|
@ -188,13 +187,7 @@ func TestServerRead(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.Remove(serverKeyFpath)
|
defer os.Remove(serverKeyFpath)
|
||||||
}
|
}
|
||||||
|
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
|
||||||
testMediaH264 := &description.Media{
|
|
||||||
Type: description.MediaTypeVideo,
|
|
||||||
Formats: []format.Format{test.FormatH264},
|
|
||||||
}
|
|
||||||
|
|
||||||
desc := &description.Session{Medias: []*description.Media{testMediaH264}}
|
|
||||||
|
|
||||||
stream, err := stream.New(
|
stream, err := stream.New(
|
||||||
1460,
|
1460,
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ type conn struct {
|
||||||
runOnConnectRestart bool
|
runOnConnectRestart bool
|
||||||
runOnDisconnect string
|
runOnDisconnect string
|
||||||
externalCmdPool *externalcmd.Pool
|
externalCmdPool *externalcmd.Pool
|
||||||
pathManager defs.PathManager
|
pathManager serverPathManager
|
||||||
rconn *gortsplib.ServerConn
|
rconn *gortsplib.ServerConn
|
||||||
rserver *gortsplib.Server
|
rserver *gortsplib.Server
|
||||||
parent *Server
|
parent *Server
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/bluenviron/mediamtx/internal/defs"
|
"github.com/bluenviron/mediamtx/internal/defs"
|
||||||
"github.com/bluenviron/mediamtx/internal/externalcmd"
|
"github.com/bluenviron/mediamtx/internal/externalcmd"
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
|
"github.com/bluenviron/mediamtx/internal/stream"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrConnNotFound is returned when a connection is not found.
|
// ErrConnNotFound is returned when a connection is not found.
|
||||||
|
|
@ -45,6 +46,12 @@ func printAddresses(srv *gortsplib.Server) string {
|
||||||
return strings.Join(ret, ", ")
|
return strings.Join(ret, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type serverPathManager interface {
|
||||||
|
Describe(req defs.PathDescribeReq) defs.PathDescribeRes
|
||||||
|
AddPublisher(_ defs.PathAddPublisherReq) (defs.Path, error)
|
||||||
|
AddReader(_ defs.PathAddReaderReq) (defs.Path, *stream.Stream, error)
|
||||||
|
}
|
||||||
|
|
||||||
type serverParent interface {
|
type serverParent interface {
|
||||||
logger.Writer
|
logger.Writer
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +79,7 @@ type Server struct {
|
||||||
RunOnConnectRestart bool
|
RunOnConnectRestart bool
|
||||||
RunOnDisconnect string
|
RunOnDisconnect string
|
||||||
ExternalCmdPool *externalcmd.Pool
|
ExternalCmdPool *externalcmd.Pool
|
||||||
PathManager defs.PathManager
|
PathManager serverPathManager
|
||||||
Parent serverParent
|
Parent serverParent
|
||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|
|
||||||
264
internal/servers/rtsp/server_test.go
Normal file
264
internal/servers/rtsp/server_test.go
Normal file
|
|
@ -0,0 +1,264 @@
|
||||||
|
package rtsp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
||||||
|
"github.com/bluenviron/mediamtx/internal/asyncwriter"
|
||||||
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
|
"github.com/bluenviron/mediamtx/internal/defs"
|
||||||
|
"github.com/bluenviron/mediamtx/internal/externalcmd"
|
||||||
|
"github.com/bluenviron/mediamtx/internal/stream"
|
||||||
|
"github.com/bluenviron/mediamtx/internal/test"
|
||||||
|
"github.com/bluenviron/mediamtx/internal/unit"
|
||||||
|
"github.com/pion/rtp"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dummyPath struct {
|
||||||
|
stream *stream.Stream
|
||||||
|
streamCreated chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dummyPath) Name() string {
|
||||||
|
return "teststream"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dummyPath) SafeConf() *conf.Path {
|
||||||
|
return &conf.Path{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dummyPath) ExternalCmdEnv() externalcmd.Environment {
|
||||||
|
return externalcmd.Environment{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dummyPath) StartPublisher(req defs.PathStartPublisherReq) (*stream.Stream, error) {
|
||||||
|
var err error
|
||||||
|
p.stream, err = stream.New(
|
||||||
|
1460,
|
||||||
|
req.Desc,
|
||||||
|
true,
|
||||||
|
test.NilLogger{},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
close(p.streamCreated)
|
||||||
|
return p.stream, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dummyPath) StopPublisher(_ defs.PathStopPublisherReq) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dummyPath) RemovePublisher(_ defs.PathRemovePublisherReq) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) {
|
||||||
|
}
|
||||||
|
|
||||||
|
type dummyPathManager struct {
|
||||||
|
path *dummyPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *dummyPathManager) Describe(_ defs.PathDescribeReq) defs.PathDescribeRes {
|
||||||
|
return defs.PathDescribeRes{
|
||||||
|
Path: pm.path,
|
||||||
|
Stream: pm.path.stream,
|
||||||
|
Redirect: "",
|
||||||
|
Err: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *dummyPathManager) AddPublisher(_ defs.PathAddPublisherReq) (defs.Path, error) {
|
||||||
|
return pm.path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *dummyPathManager) AddReader(_ defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
|
||||||
|
return pm.path, pm.path.stream, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerPublish(t *testing.T) {
|
||||||
|
path := &dummyPath{
|
||||||
|
streamCreated: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
pathManager := &dummyPathManager{path: path}
|
||||||
|
|
||||||
|
s := &Server{
|
||||||
|
Address: "127.0.0.1:8557",
|
||||||
|
AuthMethods: []headers.AuthMethod{headers.AuthBasic},
|
||||||
|
ReadTimeout: conf.StringDuration(10 * time.Second),
|
||||||
|
WriteTimeout: conf.StringDuration(10 * time.Second),
|
||||||
|
WriteQueueSize: 512,
|
||||||
|
UseUDP: false,
|
||||||
|
UseMulticast: false,
|
||||||
|
RTPAddress: "",
|
||||||
|
RTCPAddress: "",
|
||||||
|
MulticastIPRange: "",
|
||||||
|
MulticastRTPPort: 0,
|
||||||
|
MulticastRTCPPort: 0,
|
||||||
|
IsTLS: false,
|
||||||
|
ServerCert: "",
|
||||||
|
ServerKey: "",
|
||||||
|
RTSPAddress: "",
|
||||||
|
Protocols: map[conf.Protocol]struct{}{conf.Protocol(gortsplib.TransportTCP): {}},
|
||||||
|
RunOnConnect: "",
|
||||||
|
RunOnConnectRestart: false,
|
||||||
|
RunOnDisconnect: "",
|
||||||
|
ExternalCmdPool: nil,
|
||||||
|
PathManager: pathManager,
|
||||||
|
Parent: &test.NilLogger{},
|
||||||
|
}
|
||||||
|
err := s.Initialize()
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
source := gortsplib.Client{}
|
||||||
|
|
||||||
|
media0 := test.UniqueMediaH264()
|
||||||
|
|
||||||
|
err = source.StartRecording(
|
||||||
|
"rtsp://testpublisher:testpass@127.0.0.1:8557/teststream?param=value",
|
||||||
|
&description.Session{Medias: []*description.Media{media0}})
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer source.Close()
|
||||||
|
|
||||||
|
<-path.streamCreated
|
||||||
|
|
||||||
|
aw := asyncwriter.New(512, &test.NilLogger{})
|
||||||
|
|
||||||
|
recv := make(chan struct{})
|
||||||
|
|
||||||
|
path.stream.AddReader(aw,
|
||||||
|
path.stream.Desc().Medias[0],
|
||||||
|
path.stream.Desc().Medias[0].Formats[0],
|
||||||
|
func(u unit.Unit) error {
|
||||||
|
require.Equal(t, [][]byte{
|
||||||
|
test.FormatH264.SPS,
|
||||||
|
test.FormatH264.PPS,
|
||||||
|
{5, 2, 3, 4},
|
||||||
|
}, u.(*unit.H264).AU)
|
||||||
|
close(recv)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
err = source.WritePacketRTP(media0, &rtp.Packet{
|
||||||
|
Header: rtp.Header{
|
||||||
|
Version: 2,
|
||||||
|
Marker: true,
|
||||||
|
PayloadType: 96,
|
||||||
|
SequenceNumber: 123,
|
||||||
|
Timestamp: 45343,
|
||||||
|
SSRC: 563423,
|
||||||
|
},
|
||||||
|
Payload: []byte{5, 2, 3, 4},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
aw.Start()
|
||||||
|
<-recv
|
||||||
|
aw.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerRead(t *testing.T) {
|
||||||
|
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
|
||||||
|
|
||||||
|
stream, err := stream.New(
|
||||||
|
1460,
|
||||||
|
desc,
|
||||||
|
true,
|
||||||
|
test.NilLogger{},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
path := &dummyPath{stream: stream}
|
||||||
|
|
||||||
|
pathManager := &dummyPathManager{path: path}
|
||||||
|
|
||||||
|
s := &Server{
|
||||||
|
Address: "127.0.0.1:8557",
|
||||||
|
AuthMethods: []headers.AuthMethod{headers.AuthBasic},
|
||||||
|
ReadTimeout: conf.StringDuration(10 * time.Second),
|
||||||
|
WriteTimeout: conf.StringDuration(10 * time.Second),
|
||||||
|
WriteQueueSize: 512,
|
||||||
|
UseUDP: false,
|
||||||
|
UseMulticast: false,
|
||||||
|
RTPAddress: "",
|
||||||
|
RTCPAddress: "",
|
||||||
|
MulticastIPRange: "",
|
||||||
|
MulticastRTPPort: 0,
|
||||||
|
MulticastRTCPPort: 0,
|
||||||
|
IsTLS: false,
|
||||||
|
ServerCert: "",
|
||||||
|
ServerKey: "",
|
||||||
|
RTSPAddress: "",
|
||||||
|
Protocols: map[conf.Protocol]struct{}{conf.Protocol(gortsplib.TransportTCP): {}},
|
||||||
|
RunOnConnect: "",
|
||||||
|
RunOnConnectRestart: false,
|
||||||
|
RunOnDisconnect: "",
|
||||||
|
ExternalCmdPool: nil,
|
||||||
|
PathManager: pathManager,
|
||||||
|
Parent: &test.NilLogger{},
|
||||||
|
}
|
||||||
|
err = s.Initialize()
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
reader := gortsplib.Client{}
|
||||||
|
|
||||||
|
u, err := base.ParseURL("rtsp://testreader:testpass@127.0.0.1:8557/teststream?param=value")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = reader.Start(u.Scheme, u.Host)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
desc2, _, err := reader.Describe(u)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = reader.SetupAll(desc2.BaseURL, desc2.Medias)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
recv := make(chan struct{})
|
||||||
|
|
||||||
|
reader.OnPacketRTPAny(func(m *description.Media, f format.Format, p *rtp.Packet) {
|
||||||
|
require.Equal(t, &rtp.Packet{
|
||||||
|
Header: rtp.Header{
|
||||||
|
Version: 2,
|
||||||
|
Marker: true,
|
||||||
|
PayloadType: 96,
|
||||||
|
SequenceNumber: p.SequenceNumber,
|
||||||
|
Timestamp: 0,
|
||||||
|
SSRC: p.SSRC,
|
||||||
|
CSRC: []uint32{},
|
||||||
|
},
|
||||||
|
Payload: []byte{
|
||||||
|
0x18, 0x00, 0x19, 0x67, 0x42, 0xc0, 0x28, 0xd9,
|
||||||
|
0x00, 0x78, 0x02, 0x27, 0xe5, 0x84, 0x00, 0x00,
|
||||||
|
0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0xf0,
|
||||||
|
0x3c, 0x60, 0xc9, 0x20, 0x00, 0x04, 0x08, 0x06,
|
||||||
|
0x07, 0x08, 0x00, 0x04, 0x05, 0x02, 0x03, 0x04,
|
||||||
|
},
|
||||||
|
}, p)
|
||||||
|
close(recv)
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err = reader.Play(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
stream.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{
|
||||||
|
Base: unit.Base{
|
||||||
|
NTP: time.Time{},
|
||||||
|
},
|
||||||
|
AU: [][]byte{
|
||||||
|
{5, 2, 3, 4}, // IDR
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
<-recv
|
||||||
|
}
|
||||||
|
|
@ -29,7 +29,7 @@ type session struct {
|
||||||
rconn *gortsplib.ServerConn
|
rconn *gortsplib.ServerConn
|
||||||
rserver *gortsplib.Server
|
rserver *gortsplib.Server
|
||||||
externalCmdPool *externalcmd.Pool
|
externalCmdPool *externalcmd.Pool
|
||||||
pathManager defs.PathManager
|
pathManager serverPathManager
|
||||||
parent *Server
|
parent *Server
|
||||||
|
|
||||||
uuid uuid.UUID
|
uuid uuid.UUID
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
|
||||||
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
||||||
"github.com/bluenviron/mediamtx/internal/asyncwriter"
|
"github.com/bluenviron/mediamtx/internal/asyncwriter"
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
|
|
@ -167,12 +166,7 @@ func TestServerRead(t *testing.T) {
|
||||||
externalCmdPool := externalcmd.NewPool()
|
externalCmdPool := externalcmd.NewPool()
|
||||||
defer externalCmdPool.Close()
|
defer externalCmdPool.Close()
|
||||||
|
|
||||||
testMediaH264 := &description.Media{
|
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
|
||||||
Type: description.MediaTypeVideo,
|
|
||||||
Formats: []format.Format{test.FormatH264},
|
|
||||||
}
|
|
||||||
|
|
||||||
desc := &description.Session{Medias: []*description.Media{testMediaH264}}
|
|
||||||
|
|
||||||
stream, err := stream.New(
|
stream, err := stream.New(
|
||||||
1460,
|
1460,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
|
||||||
"github.com/bluenviron/mediamtx/internal/asyncwriter"
|
"github.com/bluenviron/mediamtx/internal/asyncwriter"
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
"github.com/bluenviron/mediamtx/internal/defs"
|
"github.com/bluenviron/mediamtx/internal/defs"
|
||||||
|
|
@ -89,7 +88,7 @@ func (pm *dummyPathManager) AddReader(req defs.PathAddReaderReq) (defs.Path, *st
|
||||||
|
|
||||||
func TestServerStaticPages(t *testing.T) {
|
func TestServerStaticPages(t *testing.T) {
|
||||||
s := &Server{
|
s := &Server{
|
||||||
Address: "127.0.0.1:8889",
|
Address: "127.0.0.1:8886",
|
||||||
Encryption: false,
|
Encryption: false,
|
||||||
ServerKey: "",
|
ServerKey: "",
|
||||||
ServerCert: "",
|
ServerCert: "",
|
||||||
|
|
@ -115,7 +114,7 @@ func TestServerStaticPages(t *testing.T) {
|
||||||
|
|
||||||
for _, path := range []string{"/stream", "/stream/publish", "/publish"} {
|
for _, path := range []string{"/stream", "/stream/publish", "/publish"} {
|
||||||
func() {
|
func() {
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:8889"+path, nil)
|
req, err := http.NewRequest(http.MethodGet, "http://localhost:8886"+path, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
res, err := hc.Do(req)
|
res, err := hc.Do(req)
|
||||||
|
|
@ -135,7 +134,7 @@ func TestServerPublish(t *testing.T) {
|
||||||
pathManager := &dummyPathManager{path: path}
|
pathManager := &dummyPathManager{path: path}
|
||||||
|
|
||||||
s := &Server{
|
s := &Server{
|
||||||
Address: "127.0.0.1:8889",
|
Address: "127.0.0.1:8886",
|
||||||
Encryption: false,
|
Encryption: false,
|
||||||
ServerKey: "",
|
ServerKey: "",
|
||||||
ServerCert: "",
|
ServerCert: "",
|
||||||
|
|
@ -161,7 +160,7 @@ func TestServerPublish(t *testing.T) {
|
||||||
|
|
||||||
// preflight requests must always work, without authentication
|
// preflight requests must always work, without authentication
|
||||||
func() {
|
func() {
|
||||||
req, err := http.NewRequest(http.MethodOptions, "http://localhost:8889/teststream/whip", nil)
|
req, err := http.NewRequest(http.MethodOptions, "http://localhost:8886/teststream/whip", nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
req.Header.Set("Access-Control-Request-Method", "OPTIONS")
|
req.Header.Set("Access-Control-Request-Method", "OPTIONS")
|
||||||
|
|
@ -177,7 +176,7 @@ func TestServerPublish(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ur := "http://"
|
ur := "http://"
|
||||||
ur += "localhost:8889/teststream/whip?param=value"
|
ur += "localhost:8886/teststream/whip?param=value"
|
||||||
|
|
||||||
su, err := url.Parse(ur)
|
su, err := url.Parse(ur)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -216,7 +215,7 @@ func TestServerPublish(t *testing.T) {
|
||||||
path.stream.Desc().Medias[0].Formats[0],
|
path.stream.Desc().Medias[0].Formats[0],
|
||||||
func(u unit.Unit) error {
|
func(u unit.Unit) error {
|
||||||
require.Equal(t, [][]byte{
|
require.Equal(t, [][]byte{
|
||||||
{2},
|
{1},
|
||||||
}, u.(*unit.H264).AU)
|
}, u.(*unit.H264).AU)
|
||||||
close(recv)
|
close(recv)
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -231,7 +230,7 @@ func TestServerPublish(t *testing.T) {
|
||||||
Timestamp: 45343,
|
Timestamp: 45343,
|
||||||
SSRC: 563423,
|
SSRC: 563423,
|
||||||
},
|
},
|
||||||
Payload: []byte{2},
|
Payload: []byte{1},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|
@ -241,17 +240,7 @@ func TestServerPublish(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServerRead(t *testing.T) {
|
func TestServerRead(t *testing.T) {
|
||||||
testMediaH264 := &description.Media{
|
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
|
||||||
Type: description.MediaTypeVideo,
|
|
||||||
Formats: []format.Format{test.FormatH264},
|
|
||||||
}
|
|
||||||
|
|
||||||
/*testMediaAAC := &description.Media{
|
|
||||||
Type: description.MediaTypeVideo,
|
|
||||||
Formats: []format.Format{test.FormatMPEG4Audio},
|
|
||||||
}*/
|
|
||||||
|
|
||||||
desc := &description.Session{Medias: []*description.Media{testMediaH264}}
|
|
||||||
|
|
||||||
stream, err := stream.New(
|
stream, err := stream.New(
|
||||||
1460,
|
1460,
|
||||||
|
|
@ -266,7 +255,7 @@ func TestServerRead(t *testing.T) {
|
||||||
pathManager := &dummyPathManager{path: path}
|
pathManager := &dummyPathManager{path: path}
|
||||||
|
|
||||||
s := &Server{
|
s := &Server{
|
||||||
Address: "127.0.0.1:8889",
|
Address: "127.0.0.1:8886",
|
||||||
Encryption: false,
|
Encryption: false,
|
||||||
ServerKey: "",
|
ServerKey: "",
|
||||||
ServerCert: "",
|
ServerCert: "",
|
||||||
|
|
@ -289,7 +278,7 @@ func TestServerRead(t *testing.T) {
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
ur := "http://"
|
ur := "http://"
|
||||||
ur += "localhost:8889/teststream/whep?param=value"
|
ur += "localhost:8886/teststream/whep?param=value"
|
||||||
|
|
||||||
u, err := url.Parse(ur)
|
u, err := url.Parse(ur)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -357,7 +346,7 @@ func TestServerReadNotFound(t *testing.T) {
|
||||||
pathManager := &dummyPathManager{}
|
pathManager := &dummyPathManager{}
|
||||||
|
|
||||||
s := &Server{
|
s := &Server{
|
||||||
Address: "127.0.0.1:8889",
|
Address: "127.0.0.1:8886",
|
||||||
Encryption: false,
|
Encryption: false,
|
||||||
ServerKey: "",
|
ServerKey: "",
|
||||||
ServerCert: "",
|
ServerCert: "",
|
||||||
|
|
@ -381,7 +370,7 @@ func TestServerReadNotFound(t *testing.T) {
|
||||||
|
|
||||||
hc := &http.Client{Transport: &http.Transport{}}
|
hc := &http.Client{Transport: &http.Transport{}}
|
||||||
|
|
||||||
iceServers, err := webrtc.WHIPOptionsICEServers(context.Background(), hc, "http://localhost:8889/nonexisting/whep")
|
iceServers, err := webrtc.WHIPOptionsICEServers(context.Background(), hc, "http://localhost:8886/nonexisting/whep")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pc, err := pwebrtc.NewPeerConnection(pwebrtc.Configuration{
|
pc, err := pwebrtc.NewPeerConnection(pwebrtc.Configuration{
|
||||||
|
|
@ -397,7 +386,7 @@ func TestServerReadNotFound(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodPost,
|
req, err := http.NewRequest(http.MethodPost,
|
||||||
"http://localhost:8889/nonexisting/whep", bytes.NewReader([]byte(offer.SDP)))
|
"http://localhost:8886/nonexisting/whep", bytes.NewReader([]byte(offer.SDP)))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/sdp")
|
req.Header.Set("Content-Type", "application/sdp")
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/auth"
|
"github.com/bluenviron/gortsplib/v4/pkg/auth"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
|
@ -38,11 +37,6 @@ func (sh *testServer) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Respo
|
||||||
return sh.onPlay(ctx)
|
return sh.onPlay(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
var testMediaH264 = &description.Media{
|
|
||||||
Type: description.MediaTypeVideo,
|
|
||||||
Formats: []format.Format{test.FormatH264},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSource(t *testing.T) {
|
func TestSource(t *testing.T) {
|
||||||
for _, source := range []string{
|
for _, source := range []string{
|
||||||
"udp",
|
"udp",
|
||||||
|
|
@ -55,6 +49,8 @@ func TestSource(t *testing.T) {
|
||||||
nonce, err := auth.GenerateNonce()
|
nonce, err := auth.GenerateNonce()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
media0 := test.UniqueMediaH264()
|
||||||
|
|
||||||
s := gortsplib.Server{
|
s := gortsplib.Server{
|
||||||
Handler: &testServer{
|
Handler: &testServer{
|
||||||
onDescribe: func(ctx *gortsplib.ServerHandlerOnDescribeCtx,
|
onDescribe: func(ctx *gortsplib.ServerHandlerOnDescribeCtx,
|
||||||
|
|
@ -81,7 +77,7 @@ func TestSource(t *testing.T) {
|
||||||
onPlay: func(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
onPlay: func(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
err := stream.WritePacketRTP(testMediaH264, &rtp.Packet{
|
err := stream.WritePacketRTP(media0, &rtp.Packet{
|
||||||
Header: rtp.Header{
|
Header: rtp.Header{
|
||||||
Version: 0x02,
|
Version: 0x02,
|
||||||
PayloadType: 96,
|
PayloadType: 96,
|
||||||
|
|
@ -127,7 +123,7 @@ func TestSource(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{testMediaH264}})
|
stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{media0}})
|
||||||
defer stream.Close()
|
defer stream.Close()
|
||||||
|
|
||||||
var te *test.SourceTester
|
var te *test.SourceTester
|
||||||
|
|
@ -180,6 +176,8 @@ func TestRTSPSourceNoPassword(t *testing.T) {
|
||||||
nonce, err := auth.GenerateNonce()
|
nonce, err := auth.GenerateNonce()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
media0 := test.UniqueMediaH264()
|
||||||
|
|
||||||
s := gortsplib.Server{
|
s := gortsplib.Server{
|
||||||
Handler: &testServer{
|
Handler: &testServer{
|
||||||
onDescribe: func(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
onDescribe: func(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||||
|
|
@ -200,7 +198,7 @@ func TestRTSPSourceNoPassword(t *testing.T) {
|
||||||
onSetup: func(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
onSetup: func(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
err := stream.WritePacketRTP(testMediaH264, &rtp.Packet{
|
err := stream.WritePacketRTP(media0, &rtp.Packet{
|
||||||
Header: rtp.Header{
|
Header: rtp.Header{
|
||||||
Version: 0x02,
|
Version: 0x02,
|
||||||
PayloadType: 96,
|
PayloadType: 96,
|
||||||
|
|
@ -231,7 +229,7 @@ func TestRTSPSourceNoPassword(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{testMediaH264}})
|
stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{media0}})
|
||||||
defer stream.Close()
|
defer stream.Close()
|
||||||
|
|
||||||
var sp conf.RTSPTransport
|
var sp conf.RTSPTransport
|
||||||
|
|
@ -261,6 +259,8 @@ func TestRTSPSourceRange(t *testing.T) {
|
||||||
t.Run(ca, func(t *testing.T) {
|
t.Run(ca, func(t *testing.T) {
|
||||||
var stream *gortsplib.ServerStream
|
var stream *gortsplib.ServerStream
|
||||||
|
|
||||||
|
media0 := test.UniqueMediaH264()
|
||||||
|
|
||||||
s := gortsplib.Server{
|
s := gortsplib.Server{
|
||||||
Handler: &testServer{
|
Handler: &testServer{
|
||||||
onDescribe: func(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
onDescribe: func(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||||
|
|
@ -287,7 +287,7 @@ func TestRTSPSourceRange(t *testing.T) {
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
err := stream.WritePacketRTP(testMediaH264, &rtp.Packet{
|
err := stream.WritePacketRTP(media0, &rtp.Packet{
|
||||||
Header: rtp.Header{
|
Header: rtp.Header{
|
||||||
Version: 0x02,
|
Version: 0x02,
|
||||||
PayloadType: 96,
|
PayloadType: 96,
|
||||||
|
|
@ -313,7 +313,7 @@ func TestRTSPSourceRange(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{testMediaH264}})
|
stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{media0}})
|
||||||
defer stream.Close()
|
defer stream.Close()
|
||||||
|
|
||||||
cnf := &conf.Path{}
|
cnf := &conf.Path{}
|
||||||
|
|
|
||||||
28
internal/test/medias.go
Normal file
28
internal/test/medias.go
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MediaH264 is a test H264 media.
|
||||||
|
var MediaH264 = UniqueMediaH264()
|
||||||
|
|
||||||
|
// MediaMPEG4Audio is a test MPEG-4 audio media.
|
||||||
|
var MediaMPEG4Audio = UniqueMediaMPEG4Audio()
|
||||||
|
|
||||||
|
// UniqueMediaH264 is a test H264 media.
|
||||||
|
func UniqueMediaH264() *description.Media {
|
||||||
|
return &description.Media{
|
||||||
|
Type: description.MediaTypeVideo,
|
||||||
|
Formats: []format.Format{FormatH264},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniqueMediaMPEG4Audio is a test MPEG-4 audio media.
|
||||||
|
func UniqueMediaMPEG4Audio() *description.Media {
|
||||||
|
return &description.Media{
|
||||||
|
Type: description.MediaTypeAudio,
|
||||||
|
Formats: []format.Format{FormatMPEG4Audio},
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue