Browse Source

move RTSP tests into internal/servers/rtsp (#3049)

pull/3053/head
Alessandro Ros 1 year ago committed by GitHub
parent
commit
c7bdcea741
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 67
      internal/conf/credential.go
  2. 68
      internal/conf/credential_test.go
  3. 14
      internal/conf/path.go
  4. 25
      internal/core/api_test.go
  5. 6
      internal/core/auth.go
  6. 155
      internal/core/auth_test.go
  7. 8
      internal/core/core_test.go
  8. 13
      internal/core/metrics_test.go
  9. 3
      internal/core/path_manager_test.go
  10. 133
      internal/core/path_test.go
  11. 441
      internal/core/rtsp_server_test.go
  12. 14
      internal/defs/path_manager.go
  13. 19
      internal/servers/hls/server_test.go
  14. 9
      internal/servers/rtmp/server_test.go
  15. 2
      internal/servers/rtsp/conn.go
  16. 9
      internal/servers/rtsp/server.go
  17. 264
      internal/servers/rtsp/server_test.go
  18. 2
      internal/servers/rtsp/session.go
  19. 8
      internal/servers/srt/server_test.go
  20. 37
      internal/servers/webrtc/server_test.go
  21. 24
      internal/staticsources/rtsp/source_test.go
  22. 28
      internal/test/medias.go

67
internal/conf/credential.go

@ -18,14 +18,18 @@ var ( @@ -18,14 +18,18 @@ var (
const plainCredentialSupportedChars = "A-Z,0-9,!,$,(,),*,+,.,;,<,=,>,[,],^,_,-,\",\",@,#,&"
// Credential is a parameter that is used as username or password.
type Credential struct {
value string
func sha256Base64(in string) string {
h := sha256.New()
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.
func (d Credential) MarshalJSON() ([]byte, error) {
return json.Marshal(d.value)
return json.Marshal(string(d))
}
// UnmarshalJSON implements json.Unmarshaler.
@ -35,9 +39,7 @@ func (d *Credential) UnmarshalJSON(b []byte) error { @@ -35,9 +39,7 @@ func (d *Credential) UnmarshalJSON(b []byte) error {
return err
}
*d = Credential{
value: in,
}
*d = Credential(in)
return d.validate()
}
@ -47,72 +49,57 @@ func (d *Credential) UnmarshalEnv(_ string, v string) error { @@ -47,72 +49,57 @@ func (d *Credential) UnmarshalEnv(_ string, v string) error {
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.
func (d *Credential) IsSha256() bool {
return d.value != "" && strings.HasPrefix(d.value, "sha256:")
func (d Credential) IsSha256() bool {
return strings.HasPrefix(string(d), "sha256:")
}
// IsArgon2 returns true if the credential is an argon2 hash.
func (d *Credential) IsArgon2() bool {
return d.value != "" && strings.HasPrefix(d.value, "argon2:")
func (d Credential) IsArgon2() bool {
return strings.HasPrefix(string(d), "argon2:")
}
// 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()
}
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.
func (d *Credential) Check(guess string) bool {
func (d Credential) Check(guess string) bool {
if d.IsSha256() {
return d.value[len("sha256:"):] == sha256Base64(guess)
return string(d)[len("sha256:"):] == sha256Base64(guess)
}
if d.IsArgon2() {
// TODO: remove matthewhartstonge/argon2 when this PR gets merged into mainline Go:
// 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
}
if d.IsEmpty() {
// when no credential is set, any value is valid
return true
if d != "" {
return string(d) == guess
}
return d.value == guess
return true
}
func (d *Credential) validate() error {
if !d.IsEmpty() {
func (d Credential) validate() error {
if d != "" {
switch {
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")
}
case d.IsArgon2():
// TODO: remove matthewhartstonge/argon2 when this PR gets merged into mainline Go:
// 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 {
return fmt.Errorf("invalid argon2 hash: %w", err)
}
default:
if !rePlainCredential.MatchString(d.value) {
if !rePlainCredential.MatchString(string(d)) {
return fmt.Errorf("credential contains unsupported characters. Supported are: %s", plainCredentialSupportedChars)
}
}

68
internal/conf/credential_test.go

@ -8,7 +8,7 @@ import ( @@ -8,7 +8,7 @@ import (
func TestCredential(t *testing.T) {
t.Run("MarshalJSON", func(t *testing.T) {
cred := Credential{value: "password"}
cred := Credential("password")
expectedJSON := []byte(`"password"`)
actualJSON, err := cred.MarshalJSON()
assert.NoError(t, err)
@ -16,7 +16,7 @@ func TestCredential(t *testing.T) { @@ -16,7 +16,7 @@ func TestCredential(t *testing.T) {
})
t.Run("UnmarshalJSON", func(t *testing.T) {
expectedCred := Credential{value: "password"}
expectedCred := Credential("password")
jsonData := []byte(`"password"`)
var actualCred Credential
err := actualCred.UnmarshalJSON(jsonData)
@ -25,79 +25,63 @@ func TestCredential(t *testing.T) { @@ -25,79 +25,63 @@ func TestCredential(t *testing.T) {
})
t.Run("UnmarshalEnv", func(t *testing.T) {
cred := Credential{}
cred := Credential("")
err := cred.UnmarshalEnv("", "password")
assert.NoError(t, err)
assert.Equal(t, "password", cred.value)
})
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())
assert.Equal(t, Credential("password"), cred)
})
t.Run("IsSha256", func(t *testing.T) {
cred := Credential{}
cred := Credential("")
assert.False(t, cred.IsSha256())
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.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"
assert.False(t, cred.IsSha256())
assert.True(t, cred.IsHashed())
})
t.Run("IsArgon2", func(t *testing.T) {
cred := Credential{}
cred := Credential("")
assert.False(t, cred.IsArgon2())
assert.False(t, cred.IsHashed())
cred.value = "sha256:j1tsRqDEw9xvq/D7/9tMx6Jh/jMhk3UfjwIB2f1zgMo="
cred = "sha256:j1tsRqDEw9xvq/D7/9tMx6Jh/jMhk3UfjwIB2f1zgMo="
assert.False(t, cred.IsArgon2())
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"
assert.True(t, cred.IsArgon2())
assert.True(t, cred.IsHashed())
})
t.Run("Check-plain", func(t *testing.T) {
cred := Credential{value: "password"}
cred := Credential("password")
assert.True(t, cred.Check("password"))
assert.False(t, cred.Check("wrongpassword"))
})
t.Run("Check-sha256", func(t *testing.T) {
cred := Credential{value: "password"}
cred := Credential("password")
assert.True(t, cred.Check("password"))
assert.False(t, cred.Check("wrongpassword"))
})
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.False(t, cred.Check("notestuser"))
})
t.Run("Check-argon2", func(t *testing.T) {
cred := Credential{value: "argon2:$argon2id$v=19$m=4096,t=3," +
"p=1$MTIzNDU2Nzg$Ux/LWeTgJQPyfMMJo1myR64+o8rALHoPmlE1i/TR+58"}
cred := Credential("argon2:$argon2id$v=19$m=4096,t=3," +
"p=1$MTIzNDU2Nzg$Ux/LWeTgJQPyfMMJo1myR64+o8rALHoPmlE1i/TR+58")
assert.True(t, cred.Check("testuser"))
assert.False(t, cred.Check("notestuser"))
})
@ -105,50 +89,50 @@ func TestCredential(t *testing.T) { @@ -105,50 +89,50 @@ func TestCredential(t *testing.T) {
t.Run("validate", func(t *testing.T) {
tests := []struct {
name string
cred *Credential
cred Credential
wantErr bool
}{
{
name: "Empty credential",
cred: &Credential{value: ""},
cred: Credential(""),
wantErr: false,
},
{
name: "Valid plain credential",
cred: &Credential{value: "validPlain123"},
cred: Credential("validPlain123"),
wantErr: false,
},
{
name: "Invalid plain credential",
cred: &Credential{value: "invalid/Plain"},
cred: Credential("invalid/Plain"),
wantErr: true,
},
{
name: "Valid sha256 credential",
cred: &Credential{value: "sha256:validBase64EncodedHash=="},
cred: Credential("sha256:validBase64EncodedHash=="),
wantErr: false,
},
{
name: "Invalid sha256 credential",
cred: &Credential{value: "sha256:inval*idBase64"},
cred: Credential("sha256:inval*idBase64"),
wantErr: true,
},
{
name: "Valid Argon2 credential",
cred: &Credential{value: "argon2:$argon2id$v=19$m=4096," +
"t=3,p=1$MTIzNDU2Nzg$zarsL19s86GzUWlAkvwt4gJBFuU/A9CVuCjNI4fksow"},
cred: Credential("argon2:$argon2id$v=19$m=4096," +
"t=3,p=1$MTIzNDU2Nzg$zarsL19s86GzUWlAkvwt4gJBFuU/A9CVuCjNI4fksow"),
wantErr: false,
},
{
name: "Invalid Argon2 credential",
cred: &Credential{value: "argon2:invalid"},
cred: Credential("argon2:invalid"),
wantErr: true,
},
{
name: "Invalid Argon2 credential",
// testing argon2d errors, because it's not supported
cred: &Credential{value: "$argon2d$v=19$m=4096,t=3," +
"p=1$MTIzNDU2Nzg$Xqyd4R7LzXvvAEHaVU12+Nzf5OkHoYcwIEIIYJUDpz0"},
cred: Credential("$argon2d$v=19$m=4096,t=3," +
"p=1$MTIzNDU2Nzg$Xqyd4R7LzXvvAEHaVU12+Nzf5OkHoYcwIEIIYJUDpz0"),
wantErr: true,
},
}

14
internal/conf/path.go

@ -377,11 +377,11 @@ func (pconf *Path) validate(conf *Conf, name string) error { @@ -377,11 +377,11 @@ func (pconf *Path) validate(conf *Conf, name string) error {
// Authentication
if (!pconf.PublishUser.IsEmpty() && pconf.PublishPass.IsEmpty()) ||
(pconf.PublishUser.IsEmpty() && !pconf.PublishPass.IsEmpty()) {
if (pconf.PublishUser != "" && pconf.PublishPass == "") ||
(pconf.PublishUser == "" && pconf.PublishPass != "") {
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 " +
"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 { @@ -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 " +
"the stream is not provided by a publisher, but by a fixed source")
}
if (!pconf.ReadUser.IsEmpty() && pconf.ReadPass.IsEmpty()) ||
(pconf.ReadUser.IsEmpty() && !pconf.ReadPass.IsEmpty()) {
if (pconf.ReadUser != "" && pconf.ReadPass == "") ||
(pconf.ReadUser == "" && pconf.ReadPass != "") {
return fmt.Errorf("read username and password must be both filled")
}
if contains(conf.AuthMethods, headers.AuthDigest) {
@ -402,9 +402,9 @@ func (pconf *Path) validate(conf *Conf, name string) error { @@ -402,9 +402,9 @@ func (pconf *Path) validate(conf *Conf, name string) error {
}
}
if conf.ExternalAuthenticationURL != "" {
if !pconf.PublishUser.IsEmpty() ||
if pconf.PublishUser != "" ||
len(pconf.PublishIPs) > 0 ||
!pconf.ReadUser.IsEmpty() ||
pconf.ReadUser != "" ||
len(pconf.ReadIPs) > 0 {
return fmt.Errorf("credentials or IPs can't be used together with 'externalAuthenticationURL'")
}

25
internal/core/api_test.go

@ -17,7 +17,6 @@ import ( @@ -17,7 +17,6 @@ import (
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
srt "github.com/datarhei/gosrt"
"github.com/google/uuid"
@ -29,16 +28,6 @@ import ( @@ -29,16 +28,6 @@ import (
"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) {
require.NoError(t, closeFunc())
}
@ -110,14 +99,14 @@ func TestAPIPathsList(t *testing.T) { @@ -110,14 +99,14 @@ func TestAPIPathsList(t *testing.T) {
hc := &http.Client{Transport: &http.Transport{}}
media0 := testMediaH264
media0 := test.UniqueMediaH264()
source := gortsplib.Client{}
err := source.StartRecording(
"rtsp://localhost:8554/mypath",
&description.Session{Medias: []*description.Media{
media0,
testMediaAAC,
test.MediaMPEG4Audio,
}})
require.NoError(t, err)
defer source.Close()
@ -171,8 +160,8 @@ func TestAPIPathsList(t *testing.T) { @@ -171,8 +160,8 @@ func TestAPIPathsList(t *testing.T) {
source := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}}
err = source.StartRecording("rtsps://localhost:8322/mypath",
&description.Session{Medias: []*description.Media{
testMediaH264,
testMediaAAC,
test.UniqueMediaH264(),
test.UniqueMediaMPEG4Audio(),
}})
require.NoError(t, err)
defer source.Close()
@ -313,7 +302,7 @@ func TestAPIPathsGet(t *testing.T) { @@ -313,7 +302,7 @@ func TestAPIPathsGet(t *testing.T) {
if ca == "ok" || ca == "ok-nested" {
source := gortsplib.Client{}
err := source.StartRecording("rtsp://localhost:8554/"+pathName,
&description.Session{Medias: []*description.Media{testMediaH264}})
&description.Session{Medias: []*description.Media{test.UniqueMediaH264()}})
require.NoError(t, err)
defer source.Close()
@ -384,7 +373,7 @@ func TestAPIProtocolListGet(t *testing.T) { @@ -384,7 +373,7 @@ func TestAPIProtocolListGet(t *testing.T) {
hc := &http.Client{Transport: &http.Transport{}}
medi := testMediaH264
medi := test.UniqueMediaH264()
switch ca { //nolint:dupl
case "rtsp conns", "rtsp sessions":
@ -947,7 +936,7 @@ func TestAPIProtocolKick(t *testing.T) { @@ -947,7 +936,7 @@ func TestAPIProtocolKick(t *testing.T) {
hc := &http.Client{Transport: &http.Transport{}}
medi := testMediaH264
medi := test.MediaH264
switch ca {
case "rtsp":

6
internal/core/auth.go

@ -104,12 +104,12 @@ func doAuthentication( @@ -104,12 +104,12 @@ func doAuthentication(
}
}
if !pathUser.IsEmpty() {
if pathUser != "" {
if accessRequest.RTSPRequest != nil && rtspAuth.Method == headers.AuthDigest {
err := auth.Validate(
accessRequest.RTSPRequest,
pathUser.GetValue(),
pathPass.GetValue(),
string(pathUser),
string(pathPass),
accessRequest.RTSPBaseURL,
rtspAuthMethods,
"IPCAM",

155
internal/core/auth_test.go

@ -0,0 +1,155 @@ @@ -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&param=value" &&
in.Query != "user=testpublisher&pass=testpass&param=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)
}

8
internal/core/core_test.go

@ -110,11 +110,9 @@ func TestCoreHotReloading(t *testing.T) { @@ -110,11 +110,9 @@ func TestCoreHotReloading(t *testing.T) {
defer p.Close()
func() {
medi := testMediaH264
c := gortsplib.Client{}
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)")
}()
@ -126,11 +124,9 @@ func TestCoreHotReloading(t *testing.T) { @@ -126,11 +124,9 @@ func TestCoreHotReloading(t *testing.T) {
time.Sleep(1 * time.Second)
func() {
medi := testMediaH264
conn := gortsplib.Client{}
err = conn.StartRecording("rtsp://localhost:8554/test1",
&description.Session{Medias: []*description.Media{medi}})
&description.Session{Medias: []*description.Media{test.UniqueMediaH264()}})
require.NoError(t, err)
defer conn.Close()
}()

13
internal/core/metrics_test.go

@ -15,7 +15,6 @@ import ( @@ -15,7 +15,6 @@ import (
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
srt "github.com/datarhei/gosrt"
"github.com/pion/rtp"
@ -110,10 +109,7 @@ webrtc_sessions_bytes_sent 0 @@ -110,10 +109,7 @@ webrtc_sessions_bytes_sent 0
defer wg.Done()
source := gortsplib.Client{}
err := source.StartRecording("rtsp://localhost:8554/rtsp_path",
&description.Session{Medias: []*description.Media{{
Type: description.MediaTypeVideo,
Formats: []format.Format{test.FormatH264},
}}})
&description.Session{Medias: []*description.Media{test.UniqueMediaH264()}})
require.NoError(t, err)
defer source.Close()
<-terminate
@ -123,10 +119,7 @@ webrtc_sessions_bytes_sent 0 @@ -123,10 +119,7 @@ webrtc_sessions_bytes_sent 0
defer wg.Done()
source2 := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}}
err := source2.StartRecording("rtsps://localhost:8322/rtsps_path",
&description.Session{Medias: []*description.Media{{
Type: description.MediaTypeVideo,
Formats: []format.Format{test.FormatH264},
}}})
&description.Session{Medias: []*description.Media{test.UniqueMediaH264()}})
require.NoError(t, err)
defer source2.Close()
<-terminate
@ -178,7 +171,7 @@ webrtc_sessions_bytes_sent 0 @@ -178,7 +171,7 @@ webrtc_sessions_bytes_sent 0
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)
defer checkClose(t, s.Close)

3
internal/core/path_manager_test.go

@ -7,12 +7,9 @@ import ( @@ -7,12 +7,9 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/mediamtx/internal/defs"
"github.com/stretchr/testify/require"
)
var _ defs.PathManager = &pathManager{}
func TestPathAutoDeletion(t *testing.T) {
for _, ca := range []string{"describe", "setup"} {
t.Run(ca, func(t *testing.T) {

133
internal/core/path_test.go

@ -242,7 +242,7 @@ func TestPathRunOnConnect(t *testing.T) { @@ -242,7 +242,7 @@ func TestPathRunOnConnect(t *testing.T) {
err := c.StartRecording(
"rtsp://localhost:8554/test",
&description.Session{Medias: []*description.Media{testMediaH264}})
&description.Session{Medias: []*description.Media{test.UniqueMediaH264()}})
require.NoError(t, err)
defer c.Close()
@ -302,9 +302,10 @@ func TestPathRunOnReady(t *testing.T) { @@ -302,9 +302,10 @@ func TestPathRunOnReady(t *testing.T) {
defer p.Close()
c := gortsplib.Client{}
err := c.StartRecording(
"rtsp://localhost:8554/test?query=value",
&description.Session{Medias: []*description.Media{testMediaH264}})
&description.Session{Medias: []*description.Media{test.UniqueMediaH264()}})
require.NoError(t, err)
defer c.Close()
@ -339,10 +340,13 @@ func TestPathRunOnRead(t *testing.T) { @@ -339,10 +340,13 @@ func TestPathRunOnRead(t *testing.T) {
require.Equal(t, true, ok)
defer p.Close()
media0 := test.UniqueMediaH264()
source := gortsplib.Client{}
err := source.StartRecording(
"rtsp://localhost:8554/test",
&description.Session{Medias: []*description.Media{testMediaH264}})
&description.Session{Medias: []*description.Media{media0}})
require.NoError(t, err)
defer source.Close()
@ -419,7 +423,7 @@ func TestPathRunOnRead(t *testing.T) { @@ -419,7 +423,7 @@ func TestPathRunOnRead(t *testing.T) {
case <-writerTerminate:
return
}
err := source.WritePacketRTP(testMediaH264, &rtp.Packet{
err := source.WritePacketRTP(media0, &rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
@ -462,11 +466,12 @@ func TestPathMaxReaders(t *testing.T) { @@ -462,11 +466,12 @@ func TestPathMaxReaders(t *testing.T) {
defer p.Close()
source := gortsplib.Client{}
err := source.StartRecording(
"rtsp://localhost:8554/mystream",
&description.Session{Medias: []*description.Media{
testMediaH264,
testMediaAAC,
test.UniqueMediaH264(),
test.UniqueMediaMPEG4Audio(),
}})
require.NoError(t, err)
defer source.Close()
@ -507,15 +512,18 @@ func TestPathRecord(t *testing.T) { @@ -507,15 +512,18 @@ func TestPathRecord(t *testing.T) {
require.Equal(t, true, ok)
defer p.Close()
media0 := test.UniqueMediaH264()
source := gortsplib.Client{}
err = source.StartRecording(
"rtsp://localhost:8554/mystream",
&description.Session{Medias: []*description.Media{testMediaH264}})
&description.Session{Medias: []*description.Media{media0}})
require.NoError(t, err)
defer source.Close()
for i := 0; i < 4; i++ {
err := source.WritePacketRTP(testMediaH264, &rtp.Packet{
err := source.WritePacketRTP(media0, &rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
@ -550,7 +558,7 @@ func TestPathRecord(t *testing.T) { @@ -550,7 +558,7 @@ func TestPathRecord(t *testing.T) {
time.Sleep(500 * time.Millisecond)
for i := 4; i < 8; i++ {
err := source.WritePacketRTP(testMediaH264, &rtp.Packet{
err := source.WritePacketRTP(media0, &rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
@ -607,7 +615,7 @@ func TestPathFallback(t *testing.T) { @@ -607,7 +615,7 @@ func TestPathFallback(t *testing.T) {
source := gortsplib.Client{}
err := source.StartRecording("rtsp://localhost:8554/path2",
&description.Session{Medias: []*description.Media{testMediaH264}})
&description.Session{Medias: []*description.Media{test.UniqueMediaH264()}})
require.NoError(t, err)
defer source.Close()
@ -656,7 +664,7 @@ func TestPathSourceRegexp(t *testing.T) { @@ -656,7 +664,7 @@ func TestPathSourceRegexp(t *testing.T) {
require.NoError(t, err)
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()
p, ok := newInstance(
@ -680,3 +688,106 @@ func TestPathSourceRegexp(t *testing.T) { @@ -680,3 +688,106 @@ func TestPathSourceRegexp(t *testing.T) {
_, _, err = reader.Describe(u)
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
})
}
}

441
internal/core/rtsp_server_test.go

@ -1,441 +0,0 @@ @@ -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&param=value" &&
in.Query != "user=testpublisher&pass=testpass&param=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
})
}
}

14
internal/defs/path_manager.go

@ -1,14 +0,0 @@ @@ -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)
}

19
internal/servers/hls/server_test.go

@ -9,7 +9,6 @@ import ( @@ -9,7 +9,6 @@ import (
"github.com/bluenviron/gohlslib"
"github.com/bluenviron/gohlslib/pkg/codecs"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/defs"
@ -119,12 +118,7 @@ func TestServerNotFound(t *testing.T) { @@ -119,12 +118,7 @@ func TestServerNotFound(t *testing.T) {
func TestServerRead(t *testing.T) {
t.Run("always remux off", func(t *testing.T) {
testMediaH264 := &description.Media{
Type: description.MediaTypeVideo,
Formats: []format.Format{test.FormatH264},
}
desc := &description.Session{Medias: []*description.Media{testMediaH264}}
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
stream, err := stream.New(
1460,
@ -194,7 +188,7 @@ func TestServerRead(t *testing.T) { @@ -194,7 +188,7 @@ func TestServerRead(t *testing.T) {
go func() {
time.Sleep(100 * time.Millisecond)
for i := 0; i < 4; i++ {
stream.WriteUnit(testMediaH264, test.FormatH264, &unit.H264{
stream.WriteUnit(test.MediaH264, test.FormatH264, &unit.H264{
Base: unit.Base{
NTP: time.Time{},
PTS: time.Duration(i) * time.Second,
@ -210,12 +204,7 @@ func TestServerRead(t *testing.T) { @@ -210,12 +204,7 @@ func TestServerRead(t *testing.T) {
})
t.Run("always remux on", func(t *testing.T) {
testMediaH264 := &description.Media{
Type: description.MediaTypeVideo,
Formats: []format.Format{test.FormatH264},
}
desc := &description.Session{Medias: []*description.Media{testMediaH264}}
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
stream, err := stream.New(
1460,
@ -256,7 +245,7 @@ func TestServerRead(t *testing.T) { @@ -256,7 +245,7 @@ func TestServerRead(t *testing.T) {
time.Sleep(100 * time.Millisecond)
for i := 0; i < 4; i++ {
stream.WriteUnit(testMediaH264, test.FormatH264, &unit.H264{
stream.WriteUnit(test.MediaH264, test.FormatH264, &unit.H264{
Base: unit.Base{
NTP: time.Time{},
PTS: time.Duration(i) * time.Second,

9
internal/servers/rtmp/server_test.go

@ -9,7 +9,6 @@ import ( @@ -9,7 +9,6 @@ import (
"time"
"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/conf"
"github.com/bluenviron/mediamtx/internal/defs"
@ -188,13 +187,7 @@ func TestServerRead(t *testing.T) { @@ -188,13 +187,7 @@ func TestServerRead(t *testing.T) {
require.NoError(t, err)
defer os.Remove(serverKeyFpath)
}
testMediaH264 := &description.Media{
Type: description.MediaTypeVideo,
Formats: []format.Format{test.FormatH264},
}
desc := &description.Session{Medias: []*description.Media{testMediaH264}}
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
stream, err := stream.New(
1460,

2
internal/servers/rtsp/conn.go

@ -32,7 +32,7 @@ type conn struct { @@ -32,7 +32,7 @@ type conn struct {
runOnConnectRestart bool
runOnDisconnect string
externalCmdPool *externalcmd.Pool
pathManager defs.PathManager
pathManager serverPathManager
rconn *gortsplib.ServerConn
rserver *gortsplib.Server
parent *Server

9
internal/servers/rtsp/server.go

@ -21,6 +21,7 @@ import ( @@ -21,6 +21,7 @@ import (
"github.com/bluenviron/mediamtx/internal/defs"
"github.com/bluenviron/mediamtx/internal/externalcmd"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/stream"
)
// ErrConnNotFound is returned when a connection is not found.
@ -45,6 +46,12 @@ func printAddresses(srv *gortsplib.Server) string { @@ -45,6 +46,12 @@ func printAddresses(srv *gortsplib.Server) string {
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 {
logger.Writer
}
@ -72,7 +79,7 @@ type Server struct { @@ -72,7 +79,7 @@ type Server struct {
RunOnConnectRestart bool
RunOnDisconnect string
ExternalCmdPool *externalcmd.Pool
PathManager defs.PathManager
PathManager serverPathManager
Parent serverParent
ctx context.Context

264
internal/servers/rtsp/server_test.go

@ -0,0 +1,264 @@ @@ -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
}

2
internal/servers/rtsp/session.go

@ -29,7 +29,7 @@ type session struct { @@ -29,7 +29,7 @@ type session struct {
rconn *gortsplib.ServerConn
rserver *gortsplib.Server
externalCmdPool *externalcmd.Pool
pathManager defs.PathManager
pathManager serverPathManager
parent *Server
uuid uuid.UUID

8
internal/servers/srt/server_test.go

@ -6,7 +6,6 @@ import ( @@ -6,7 +6,6 @@ import (
"time"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
"github.com/bluenviron/mediamtx/internal/asyncwriter"
"github.com/bluenviron/mediamtx/internal/conf"
@ -167,12 +166,7 @@ func TestServerRead(t *testing.T) { @@ -167,12 +166,7 @@ func TestServerRead(t *testing.T) {
externalCmdPool := externalcmd.NewPool()
defer externalCmdPool.Close()
testMediaH264 := &description.Media{
Type: description.MediaTypeVideo,
Formats: []format.Format{test.FormatH264},
}
desc := &description.Session{Medias: []*description.Media{testMediaH264}}
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
stream, err := stream.New(
1460,

37
internal/servers/webrtc/server_test.go

@ -9,7 +9,6 @@ import ( @@ -9,7 +9,6 @@ import (
"time"
"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/conf"
"github.com/bluenviron/mediamtx/internal/defs"
@ -89,7 +88,7 @@ func (pm *dummyPathManager) AddReader(req defs.PathAddReaderReq) (defs.Path, *st @@ -89,7 +88,7 @@ func (pm *dummyPathManager) AddReader(req defs.PathAddReaderReq) (defs.Path, *st
func TestServerStaticPages(t *testing.T) {
s := &Server{
Address: "127.0.0.1:8889",
Address: "127.0.0.1:8886",
Encryption: false,
ServerKey: "",
ServerCert: "",
@ -115,7 +114,7 @@ func TestServerStaticPages(t *testing.T) { @@ -115,7 +114,7 @@ func TestServerStaticPages(t *testing.T) {
for _, path := range []string{"/stream", "/stream/publish", "/publish"} {
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)
res, err := hc.Do(req)
@ -135,7 +134,7 @@ func TestServerPublish(t *testing.T) { @@ -135,7 +134,7 @@ func TestServerPublish(t *testing.T) {
pathManager := &dummyPathManager{path: path}
s := &Server{
Address: "127.0.0.1:8889",
Address: "127.0.0.1:8886",
Encryption: false,
ServerKey: "",
ServerCert: "",
@ -161,7 +160,7 @@ func TestServerPublish(t *testing.T) { @@ -161,7 +160,7 @@ func TestServerPublish(t *testing.T) {
// preflight requests must always work, without authentication
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)
req.Header.Set("Access-Control-Request-Method", "OPTIONS")
@ -177,7 +176,7 @@ func TestServerPublish(t *testing.T) { @@ -177,7 +176,7 @@ func TestServerPublish(t *testing.T) {
}()
ur := "http://"
ur += "localhost:8889/teststream/whip?param=value"
ur += "localhost:8886/teststream/whip?param=value"
su, err := url.Parse(ur)
require.NoError(t, err)
@ -216,7 +215,7 @@ func TestServerPublish(t *testing.T) { @@ -216,7 +215,7 @@ func TestServerPublish(t *testing.T) {
path.stream.Desc().Medias[0].Formats[0],
func(u unit.Unit) error {
require.Equal(t, [][]byte{
{2},
{1},
}, u.(*unit.H264).AU)
close(recv)
return nil
@ -231,7 +230,7 @@ func TestServerPublish(t *testing.T) { @@ -231,7 +230,7 @@ func TestServerPublish(t *testing.T) {
Timestamp: 45343,
SSRC: 563423,
},
Payload: []byte{2},
Payload: []byte{1},
})
require.NoError(t, err)
@ -241,17 +240,7 @@ func TestServerPublish(t *testing.T) { @@ -241,17 +240,7 @@ func TestServerPublish(t *testing.T) {
}
func TestServerRead(t *testing.T) {
testMediaH264 := &description.Media{
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}}
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
stream, err := stream.New(
1460,
@ -266,7 +255,7 @@ func TestServerRead(t *testing.T) { @@ -266,7 +255,7 @@ func TestServerRead(t *testing.T) {
pathManager := &dummyPathManager{path: path}
s := &Server{
Address: "127.0.0.1:8889",
Address: "127.0.0.1:8886",
Encryption: false,
ServerKey: "",
ServerCert: "",
@ -289,7 +278,7 @@ func TestServerRead(t *testing.T) { @@ -289,7 +278,7 @@ func TestServerRead(t *testing.T) {
defer s.Close()
ur := "http://"
ur += "localhost:8889/teststream/whep?param=value"
ur += "localhost:8886/teststream/whep?param=value"
u, err := url.Parse(ur)
require.NoError(t, err)
@ -357,7 +346,7 @@ func TestServerReadNotFound(t *testing.T) { @@ -357,7 +346,7 @@ func TestServerReadNotFound(t *testing.T) {
pathManager := &dummyPathManager{}
s := &Server{
Address: "127.0.0.1:8889",
Address: "127.0.0.1:8886",
Encryption: false,
ServerKey: "",
ServerCert: "",
@ -381,7 +370,7 @@ func TestServerReadNotFound(t *testing.T) { @@ -381,7 +370,7 @@ func TestServerReadNotFound(t *testing.T) {
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)
pc, err := pwebrtc.NewPeerConnection(pwebrtc.Configuration{
@ -397,7 +386,7 @@ func TestServerReadNotFound(t *testing.T) { @@ -397,7 +386,7 @@ func TestServerReadNotFound(t *testing.T) {
require.NoError(t, err)
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)
req.Header.Set("Content-Type", "application/sdp")

24
internal/staticsources/rtsp/source_test.go

@ -10,7 +10,6 @@ import ( @@ -10,7 +10,6 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/pion/rtp"
"github.com/stretchr/testify/require"
@ -38,11 +37,6 @@ func (sh *testServer) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Respo @@ -38,11 +37,6 @@ func (sh *testServer) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Respo
return sh.onPlay(ctx)
}
var testMediaH264 = &description.Media{
Type: description.MediaTypeVideo,
Formats: []format.Format{test.FormatH264},
}
func TestSource(t *testing.T) {
for _, source := range []string{
"udp",
@ -55,6 +49,8 @@ func TestSource(t *testing.T) { @@ -55,6 +49,8 @@ func TestSource(t *testing.T) {
nonce, err := auth.GenerateNonce()
require.NoError(t, err)
media0 := test.UniqueMediaH264()
s := gortsplib.Server{
Handler: &testServer{
onDescribe: func(ctx *gortsplib.ServerHandlerOnDescribeCtx,
@ -81,7 +77,7 @@ func TestSource(t *testing.T) { @@ -81,7 +77,7 @@ func TestSource(t *testing.T) {
onPlay: func(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
go func() {
time.Sleep(100 * time.Millisecond)
err := stream.WritePacketRTP(testMediaH264, &rtp.Packet{
err := stream.WritePacketRTP(media0, &rtp.Packet{
Header: rtp.Header{
Version: 0x02,
PayloadType: 96,
@ -127,7 +123,7 @@ func TestSource(t *testing.T) { @@ -127,7 +123,7 @@ func TestSource(t *testing.T) {
require.NoError(t, err)
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()
var te *test.SourceTester
@ -180,6 +176,8 @@ func TestRTSPSourceNoPassword(t *testing.T) { @@ -180,6 +176,8 @@ func TestRTSPSourceNoPassword(t *testing.T) {
nonce, err := auth.GenerateNonce()
require.NoError(t, err)
media0 := test.UniqueMediaH264()
s := gortsplib.Server{
Handler: &testServer{
onDescribe: func(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
@ -200,7 +198,7 @@ func TestRTSPSourceNoPassword(t *testing.T) { @@ -200,7 +198,7 @@ func TestRTSPSourceNoPassword(t *testing.T) {
onSetup: func(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
go func() {
time.Sleep(100 * time.Millisecond)
err := stream.WritePacketRTP(testMediaH264, &rtp.Packet{
err := stream.WritePacketRTP(media0, &rtp.Packet{
Header: rtp.Header{
Version: 0x02,
PayloadType: 96,
@ -231,7 +229,7 @@ func TestRTSPSourceNoPassword(t *testing.T) { @@ -231,7 +229,7 @@ func TestRTSPSourceNoPassword(t *testing.T) {
require.NoError(t, err)
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()
var sp conf.RTSPTransport
@ -261,6 +259,8 @@ func TestRTSPSourceRange(t *testing.T) { @@ -261,6 +259,8 @@ func TestRTSPSourceRange(t *testing.T) {
t.Run(ca, func(t *testing.T) {
var stream *gortsplib.ServerStream
media0 := test.UniqueMediaH264()
s := gortsplib.Server{
Handler: &testServer{
onDescribe: func(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
@ -287,7 +287,7 @@ func TestRTSPSourceRange(t *testing.T) { @@ -287,7 +287,7 @@ func TestRTSPSourceRange(t *testing.T) {
go func() {
time.Sleep(100 * time.Millisecond)
err := stream.WritePacketRTP(testMediaH264, &rtp.Packet{
err := stream.WritePacketRTP(media0, &rtp.Packet{
Header: rtp.Header{
Version: 0x02,
PayloadType: 96,
@ -313,7 +313,7 @@ func TestRTSPSourceRange(t *testing.T) { @@ -313,7 +313,7 @@ func TestRTSPSourceRange(t *testing.T) {
require.NoError(t, err)
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()
cnf := &conf.Path{}

28
internal/test/medias.go

@ -0,0 +1,28 @@ @@ -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…
Cancel
Save