mirror of
https://github.com/aler9/gortsplib
synced 2025-12-24 13:38:08 +08:00
plus, allow to pick between AVP and SAVP when scheme is RTSPS and protocol is TCP. --------- Co-authored-by: aler9 <46489434+aler9@users.noreply.github.com>
312 lines
9.6 KiB
Go
312 lines
9.6 KiB
Go
package gortsplib
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/tls"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/bluenviron/gortsplib/v5/pkg/base"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/conn"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/headers"
|
|
)
|
|
|
|
// handleServerConnection handles the common server-side connection logic for the security profile tests
|
|
func handleServerConnection(t *testing.T, serverDone chan struct{}, nconn net.Conn) {
|
|
defer close(serverDone)
|
|
|
|
if nconn == nil {
|
|
return
|
|
}
|
|
defer nconn.Close()
|
|
|
|
conn := conn.NewConn(bufio.NewReader(nconn), nconn)
|
|
|
|
req, err2 := conn.ReadRequest()
|
|
require.NoError(t, err2)
|
|
require.Equal(t, base.Options, req.Method)
|
|
|
|
err2 = conn.WriteResponse(&base.Response{
|
|
StatusCode: base.StatusOK,
|
|
Header: base.Header{
|
|
"Public": base.HeaderValue{strings.Join([]string{
|
|
string(base.Announce),
|
|
string(base.Setup),
|
|
string(base.Record),
|
|
}, ", ")},
|
|
},
|
|
})
|
|
require.NoError(t, err2)
|
|
|
|
req, err2 = conn.ReadRequest()
|
|
require.NoError(t, err2)
|
|
require.Equal(t, base.Announce, req.Method)
|
|
|
|
err2 = conn.WriteResponse(&base.Response{
|
|
StatusCode: base.StatusOK,
|
|
})
|
|
require.NoError(t, err2)
|
|
}
|
|
|
|
// createSecureMedia creates a media description with secure profile (SAVP)
|
|
func createSecureMedia() *description.Media {
|
|
return &description.Media{
|
|
Type: description.MediaTypeVideo,
|
|
Profile: headers.TransportProfileSAVP, // This is the secure profile
|
|
Formats: []format.Format{&format.H264{
|
|
PayloadTyp: 96,
|
|
SPS: []byte{
|
|
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,
|
|
},
|
|
PPS: []byte{
|
|
0x44, 0x01, 0xc0, 0x25, 0x2f, 0x05, 0x32, 0x40,
|
|
},
|
|
PacketizationMode: 1,
|
|
}},
|
|
ID: "1",
|
|
Control: "trackID=0",
|
|
}
|
|
}
|
|
|
|
// createNonSecureMedia creates a media description with non-secure profile (RTP/AVP)
|
|
func createNonSecureMedia() *description.Media {
|
|
return &description.Media{
|
|
Type: description.MediaTypeVideo,
|
|
// Profile defaults to RTP/AVP which is not secure
|
|
Formats: []format.Format{&format.H264{
|
|
PayloadTyp: 96,
|
|
SPS: []byte{
|
|
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,
|
|
},
|
|
PPS: []byte{
|
|
0x44, 0x01, 0xc0, 0x25, 0x2f, 0x05, 0x32, 0x40,
|
|
},
|
|
PacketizationMode: 1,
|
|
}},
|
|
ID: "1",
|
|
Control: "trackID=0",
|
|
}
|
|
}
|
|
|
|
// createAudioMedia creates a non-secure audio media description
|
|
func createAudioMedia() *description.Media {
|
|
return &description.Media{
|
|
Type: description.MediaTypeAudio,
|
|
// Profile defaults to RTP/AVP which is not secure
|
|
Formats: []format.Format{&format.G711{
|
|
PayloadTyp: 0,
|
|
SampleRate: 8000,
|
|
ChannelCount: 1,
|
|
}},
|
|
ID: "2",
|
|
Control: "trackID=1",
|
|
}
|
|
}
|
|
|
|
// setupTLSTestServer creates a TLS listener and server goroutine for RTSPS testing
|
|
func setupTLSTestServer(t *testing.T) (net.Listener, chan struct{}) {
|
|
// Create TLS listener for RTSPS
|
|
cert, err := tls.X509KeyPair(serverCert, serverKey)
|
|
require.NoError(t, err)
|
|
|
|
l, err := tls.Listen("tcp", "localhost:0", &tls.Config{Certificates: []tls.Certificate{cert}})
|
|
require.NoError(t, err)
|
|
|
|
serverDone := make(chan struct{})
|
|
|
|
go func() {
|
|
nconn, err2 := l.Accept()
|
|
require.NoError(t, err2)
|
|
handleServerConnection(t, serverDone, nconn)
|
|
}()
|
|
|
|
return l, serverDone
|
|
}
|
|
|
|
// createTLSClientWithProtocol creates a configured RTSP client for RTSPS testing
|
|
func createTLSClientWithProtocol(addr string, protocol Protocol) *Client {
|
|
u, err := base.ParseURL("rtsps://" + addr + "/teststream")
|
|
if err != nil {
|
|
panic(err) // This should never happen in tests
|
|
}
|
|
|
|
c := &Client{
|
|
Scheme: u.Scheme,
|
|
Host: u.Host,
|
|
TLSConfig: &tls.Config{InsecureSkipVerify: true},
|
|
Protocol: &protocol,
|
|
}
|
|
|
|
return c
|
|
}
|
|
|
|
// testRTSPAnnounceWithProtocol is a helper function that tests RTSP announce with a specific protocol and media
|
|
func testRTSPAnnounceWithProtocol(t *testing.T, protocol Protocol, mediaFactory func() *description.Media) {
|
|
l, err := net.Listen("tcp", "localhost:0")
|
|
require.NoError(t, err)
|
|
defer l.Close()
|
|
|
|
serverDone := make(chan struct{})
|
|
defer func() { <-serverDone }()
|
|
|
|
go func() {
|
|
nconn, err2 := l.Accept()
|
|
require.NoError(t, err2)
|
|
handleServerConnection(t, serverDone, nconn)
|
|
}()
|
|
|
|
// Create media using the provided factory
|
|
media := mediaFactory()
|
|
|
|
desc := &description.Session{
|
|
Medias: []*description.Media{media},
|
|
}
|
|
|
|
u, err := base.ParseURL("rtsp://" + l.Addr().String() + "/teststream")
|
|
require.NoError(t, err)
|
|
|
|
c := Client{
|
|
Scheme: u.Scheme,
|
|
Host: u.Host,
|
|
}
|
|
|
|
// Set the protocol for this test
|
|
c.Protocol = &protocol
|
|
|
|
err = c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
// This should succeed - RTSP always sets secure=false regardless of media profile
|
|
_, err = c.Announce(u, desc)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestClientAnnounceSecureProfileValidation(t *testing.T) {
|
|
// Test how secure flag is determined based on different protocol/scheme/profile combinations
|
|
//
|
|
// BEHAVIOR MATRIX:
|
|
// ┌─────────┬─────────┬──────────────┬────────────────────────────┐
|
|
// │Protocol │ Scheme │Media Profile │ secure flag result │
|
|
// ├─────────┼─────────┼──────────────┼────────────────────────────┤
|
|
// │ TCP │ RTSPS │ SAVP │ true (has secure profile) │
|
|
// │ TCP │ RTSPS │ RTP/AVP │ false (no secure profile) │
|
|
// │ UDP │ RTSPS │ Any │ true (scheme is rtsps) │
|
|
// │ TCP │ RTSP │ Any │ false (scheme is rtsp) │
|
|
// │ UDP │ RTSP │ Any │ false (scheme is rtsp) │
|
|
// └─────────┴─────────┴──────────────┴────────────────────────────┘
|
|
//
|
|
// Current implementation logic:
|
|
// if (Protocol == TCP && Scheme == "rtsps") {
|
|
// secure = hasSecureProfile // Check if any media has SAVP profile
|
|
// } else {
|
|
// secure = (Scheme == "rtsps") // Based on scheme only
|
|
// }
|
|
|
|
t.Run("TCP+RTSPS with secure profile - secure=true", func(t *testing.T) {
|
|
l, serverDone := setupTLSTestServer(t)
|
|
defer l.Close()
|
|
defer func() { <-serverDone }()
|
|
|
|
// Create a media with secure profile (SAVP)
|
|
media := createSecureMedia()
|
|
desc := &description.Session{Medias: []*description.Media{media}}
|
|
|
|
c := createTLSClientWithProtocol(l.Addr().String(), ProtocolTCP)
|
|
err := c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
// This should succeed - TCP+RTSPS with secure profile sets secure=true
|
|
u, err := base.ParseURL("rtsps://" + l.Addr().String() + "/teststream")
|
|
require.NoError(t, err)
|
|
_, err = c.Announce(u, desc)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("TCP+RTSPS with non-secure profile - secure=false", func(t *testing.T) {
|
|
l, serverDone := setupTLSTestServer(t)
|
|
defer l.Close()
|
|
defer func() { <-serverDone }()
|
|
|
|
// Create a media with NON-secure profile (default RTP/AVP)
|
|
media := createNonSecureMedia()
|
|
desc := &description.Session{Medias: []*description.Media{media}}
|
|
|
|
c := createTLSClientWithProtocol(l.Addr().String(), ProtocolTCP)
|
|
err := c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
// This should succeed - TCP+RTSPS with non-secure profile sets secure=false
|
|
u, err := base.ParseURL("rtsps://" + l.Addr().String() + "/teststream")
|
|
require.NoError(t, err)
|
|
_, err = c.Announce(u, desc)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("UDP+RTSPS with any profile - secure=true", func(t *testing.T) {
|
|
l, serverDone := setupTLSTestServer(t)
|
|
defer l.Close()
|
|
defer func() { <-serverDone }()
|
|
|
|
// Create a media with secure profile (SAVP)
|
|
media := createSecureMedia()
|
|
desc := &description.Session{Medias: []*description.Media{media}}
|
|
|
|
c := createTLSClientWithProtocol(l.Addr().String(), ProtocolUDP)
|
|
err := c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
// This should succeed - UDP+RTSPS always sets secure=true regardless of media profile
|
|
u, err := base.ParseURL("rtsps://" + l.Addr().String() + "/teststream")
|
|
require.NoError(t, err)
|
|
_, err = c.Announce(u, desc)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("TCP+RTSP with any profile - secure=false", func(t *testing.T) {
|
|
// Create a media with regular profile (RTP/AVP - not secure)
|
|
testRTSPAnnounceWithProtocol(t, ProtocolTCP, createNonSecureMedia)
|
|
})
|
|
|
|
t.Run("UDP+RTSP with any profile - secure=false", func(t *testing.T) {
|
|
// Create a media with secure profile (just to show it doesn't matter for UDP+RTSP)
|
|
testRTSPAnnounceWithProtocol(t, ProtocolUDP, createSecureMedia)
|
|
})
|
|
|
|
t.Run("TCP+RTSPS with mixed profiles - secure=true if any profile is secure", func(t *testing.T) {
|
|
l, serverDone := setupTLSTestServer(t)
|
|
defer l.Close()
|
|
defer func() { <-serverDone }()
|
|
|
|
// Create multiple medias: one secure, one non-secure
|
|
mediaSecure := createSecureMedia()
|
|
mediaNonSecure := createAudioMedia()
|
|
desc := &description.Session{Medias: []*description.Media{mediaSecure, mediaNonSecure}}
|
|
|
|
c := createTLSClientWithProtocol(l.Addr().String(), ProtocolTCP)
|
|
err := c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
// This should succeed - TCP+RTSPS with mixed profiles, one secure sets secure=true
|
|
u, err := base.ParseURL("rtsps://" + l.Addr().String() + "/teststream")
|
|
require.NoError(t, err)
|
|
_, err = c.Announce(u, desc)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|