mirror of
https://github.com/aler9/gortsplib
synced 2025-09-27 11:32:08 +08:00
add Transport.Profile, media.Profile (#873)
this will allow to support AVPF in the future.
This commit is contained in:
85
client.go
85
client.go
@@ -202,9 +202,9 @@ func prepareForAnnounce(
|
|||||||
) error {
|
) error {
|
||||||
for i, m := range desc.Medias {
|
for i, m := range desc.Medias {
|
||||||
m.Control = "trackID=" + strconv.FormatInt(int64(i), 10)
|
m.Control = "trackID=" + strconv.FormatInt(int64(i), 10)
|
||||||
m.Secure = secure
|
|
||||||
|
|
||||||
if secure {
|
if secure {
|
||||||
|
m.Profile = headers.TransportProfileSAVP
|
||||||
announceDataMedia := announceData[m]
|
announceDataMedia := announceData[m]
|
||||||
|
|
||||||
ssrcs := make([]uint32, len(m.Formats))
|
ssrcs := make([]uint32, len(m.Formats))
|
||||||
@@ -232,6 +232,8 @@ func prepareForAnnounce(
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.KeyMgmtMikey = mikeyMsg
|
m.KeyMgmtMikey = mikeyMsg
|
||||||
|
} else {
|
||||||
|
m.Profile = headers.TransportProfileAVP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,8 +514,8 @@ type Client struct {
|
|||||||
lastDescribeDesc *description.Session
|
lastDescribeDesc *description.Session
|
||||||
baseURL *base.URL
|
baseURL *base.URL
|
||||||
announceData map[*description.Media]*clientAnnounceDataMedia // record
|
announceData map[*description.Media]*clientAnnounceDataMedia // record
|
||||||
effectiveTransport *Transport
|
setuppedTransport *Transport
|
||||||
effectiveSecure bool
|
setuppedProfile headers.TransportProfile
|
||||||
backChannelSetupped bool
|
backChannelSetupped bool
|
||||||
stdChannelSetupped bool
|
stdChannelSetupped bool
|
||||||
setuppedMedias map[*description.Media]*clientMedia
|
setuppedMedias map[*description.Media]*clientMedia
|
||||||
@@ -973,7 +975,7 @@ func (c *Client) reset() {
|
|||||||
c.optionsSent = false
|
c.optionsSent = false
|
||||||
c.useGetParameter = false
|
c.useGetParameter = false
|
||||||
c.baseURL = nil
|
c.baseURL = nil
|
||||||
c.effectiveTransport = nil
|
c.setuppedTransport = nil
|
||||||
c.backChannelSetupped = false
|
c.backChannelSetupped = false
|
||||||
c.stdChannelSetupped = false
|
c.stdChannelSetupped = false
|
||||||
c.setuppedMedias = nil
|
c.setuppedMedias = nil
|
||||||
@@ -1004,7 +1006,7 @@ func (c *Client) trySwitchingProtocol() error {
|
|||||||
c.reset()
|
c.reset()
|
||||||
|
|
||||||
v := TransportTCP
|
v := TransportTCP
|
||||||
c.effectiveTransport = &v
|
c.setuppedTransport = &v
|
||||||
|
|
||||||
// some Hikvision cameras require a describe before a setup
|
// some Hikvision cameras require a describe before a setup
|
||||||
_, _, err := c.doDescribe(c.lastDescribeURL)
|
_, _, err := c.doDescribe(c.lastDescribeURL)
|
||||||
@@ -1040,18 +1042,18 @@ func (c *Client) startTransportRoutines() {
|
|||||||
cm.start()
|
cm.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
if *c.effectiveTransport == TransportTCP {
|
if *c.setuppedTransport == TransportTCP {
|
||||||
c.tcpFrame = &base.InterleavedFrame{}
|
c.tcpFrame = &base.InterleavedFrame{}
|
||||||
c.tcpBuffer = make([]byte, c.MaxPacketSize+4)
|
c.tcpBuffer = make([]byte, c.MaxPacketSize+4)
|
||||||
}
|
}
|
||||||
|
|
||||||
// always enable keepalives unless we are recording with TCP
|
// always enable keepalives unless we are recording with TCP
|
||||||
if c.state == clientStatePlay || *c.effectiveTransport != TransportTCP {
|
if c.state == clientStatePlay || *c.setuppedTransport != TransportTCP {
|
||||||
c.keepAliveTimer = time.NewTimer(c.keepAlivePeriod)
|
c.keepAliveTimer = time.NewTimer(c.keepAlivePeriod)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.state == clientStatePlay && c.stdChannelSetupped {
|
if c.state == clientStatePlay && c.stdChannelSetupped {
|
||||||
switch *c.effectiveTransport {
|
switch *c.setuppedTransport {
|
||||||
case TransportUDP:
|
case TransportUDP:
|
||||||
c.checkTimeoutTimer = time.NewTimer(c.InitialUDPReadTimeout)
|
c.checkTimeoutTimer = time.NewTimer(c.InitialUDPReadTimeout)
|
||||||
c.checkTimeoutInitial = true
|
c.checkTimeoutInitial = true
|
||||||
@@ -1066,7 +1068,7 @@ func (c *Client) startTransportRoutines() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if *c.effectiveTransport == TransportTCP {
|
if *c.setuppedTransport == TransportTCP {
|
||||||
c.reader.setAllowInterleavedFrames(true)
|
c.reader.setAllowInterleavedFrames(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1281,8 +1283,8 @@ func (c *Client) isInTCPTimeout() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) doCheckTimeout() error {
|
func (c *Client) doCheckTimeout() error {
|
||||||
if *c.effectiveTransport == TransportUDP ||
|
if *c.setuppedTransport == TransportUDP ||
|
||||||
*c.effectiveTransport == TransportUDPMulticast {
|
*c.setuppedTransport == TransportUDPMulticast {
|
||||||
if c.checkTimeoutInitial && !c.backChannelSetupped && c.Transport == nil {
|
if c.checkTimeoutInitial && !c.backChannelSetupped && c.Transport == nil {
|
||||||
c.checkTimeoutInitial = false
|
c.checkTimeoutInitial = false
|
||||||
|
|
||||||
@@ -1586,20 +1588,30 @@ func (c *Client) doSetup(
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
// use transport from previous SETUP calls
|
// use transport from previous SETUP calls
|
||||||
case c.effectiveTransport != nil:
|
case c.setuppedTransport != nil:
|
||||||
transport = *c.effectiveTransport
|
transport = *c.setuppedTransport
|
||||||
th.Secure = c.effectiveSecure
|
th.Profile = c.setuppedProfile
|
||||||
|
|
||||||
// use transport from config, secure flag from server
|
// use transport from config, secure flag from server
|
||||||
case c.Transport != nil:
|
case c.Transport != nil:
|
||||||
transport = *c.Transport
|
transport = *c.Transport
|
||||||
th.Secure = medi.Secure && c.Scheme == "rtsps"
|
if isSecure(medi.Profile) && c.Scheme == "rtsps" {
|
||||||
|
th.Profile = headers.TransportProfileSAVP
|
||||||
|
} else {
|
||||||
|
th.Profile = headers.TransportProfileAVP
|
||||||
|
}
|
||||||
|
|
||||||
// try UDP if unencrypted or secure is supported by server, otherwise try TCP
|
// try
|
||||||
|
// - UDP if unencrypted or secure is supported by server
|
||||||
|
// - otherwise, TCP
|
||||||
default:
|
default:
|
||||||
th.Secure = medi.Secure && c.Scheme == "rtsps"
|
if isSecure(medi.Profile) && c.Scheme == "rtsps" {
|
||||||
|
th.Profile = headers.TransportProfileSAVP
|
||||||
|
} else {
|
||||||
|
th.Profile = headers.TransportProfileAVP
|
||||||
|
}
|
||||||
|
|
||||||
if th.Secure || c.Scheme == "rtsp" {
|
if th.Profile == headers.TransportProfileSAVP || c.Scheme == "rtsp" {
|
||||||
transport = TransportUDP
|
transport = TransportUDP
|
||||||
} else {
|
} else {
|
||||||
transport = TransportTCP
|
transport = TransportTCP
|
||||||
@@ -1609,7 +1621,7 @@ func (c *Client) doSetup(
|
|||||||
cm := &clientMedia{
|
cm := &clientMedia{
|
||||||
c: c,
|
c: c,
|
||||||
media: medi,
|
media: medi,
|
||||||
secure: th.Secure,
|
secure: isSecure(th.Profile),
|
||||||
}
|
}
|
||||||
err = cm.initialize()
|
err = cm.initialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1618,7 +1630,7 @@ func (c *Client) doSetup(
|
|||||||
|
|
||||||
switch transport {
|
switch transport {
|
||||||
case TransportUDP, TransportUDPMulticast:
|
case TransportUDP, TransportUDPMulticast:
|
||||||
if c.Scheme == "rtsps" && !th.Secure {
|
if c.Scheme == "rtsps" && !isSecure(th.Profile) {
|
||||||
cm.close()
|
cm.close()
|
||||||
return nil, fmt.Errorf("unable to setup secure UDP")
|
return nil, fmt.Errorf("unable to setup secure UDP")
|
||||||
}
|
}
|
||||||
@@ -1683,7 +1695,7 @@ func (c *Client) doSetup(
|
|||||||
header["Require"] = base.HeaderValue{"www.onvif.org/ver20/backchannel"}
|
header["Require"] = base.HeaderValue{"www.onvif.org/ver20/backchannel"}
|
||||||
}
|
}
|
||||||
|
|
||||||
if th.Secure {
|
if isSecure(th.Profile) {
|
||||||
ssrcs := make([]uint32, len(cm.formats))
|
ssrcs := make([]uint32, len(cm.formats))
|
||||||
n := 0
|
n := 0
|
||||||
for _, cf := range cm.formats {
|
for _, cf := range cm.formats {
|
||||||
@@ -1726,11 +1738,11 @@ func (c *Client) doSetup(
|
|||||||
|
|
||||||
// switch transport automatically
|
// switch transport automatically
|
||||||
if res.StatusCode == base.StatusUnsupportedTransport &&
|
if res.StatusCode == base.StatusUnsupportedTransport &&
|
||||||
c.effectiveTransport == nil && c.Transport == nil {
|
c.setuppedTransport == nil && c.Transport == nil {
|
||||||
c.OnTransportSwitch(liberrors.ErrClientSwitchToTCP2{})
|
c.OnTransportSwitch(liberrors.ErrClientSwitchToTCP2{})
|
||||||
v := TransportTCP
|
v := TransportTCP
|
||||||
c.effectiveTransport = &v
|
c.setuppedTransport = &v
|
||||||
c.effectiveSecure = th.Secure
|
c.setuppedProfile = th.Profile
|
||||||
|
|
||||||
return c.doSetup(baseURL, medi, 0, 0)
|
return c.doSetup(baseURL, medi, 0, 0)
|
||||||
}
|
}
|
||||||
@@ -1751,7 +1763,7 @@ func (c *Client) doSetup(
|
|||||||
cm.close()
|
cm.close()
|
||||||
|
|
||||||
// switch transport automatically
|
// switch transport automatically
|
||||||
if c.effectiveTransport == nil && c.Transport == nil {
|
if c.setuppedTransport == nil && c.Transport == nil {
|
||||||
c.OnTransportSwitch(liberrors.ErrClientSwitchToTCP2{})
|
c.OnTransportSwitch(liberrors.ErrClientSwitchToTCP2{})
|
||||||
|
|
||||||
c.baseURL = baseURL
|
c.baseURL = baseURL
|
||||||
@@ -1759,8 +1771,8 @@ func (c *Client) doSetup(
|
|||||||
c.reset()
|
c.reset()
|
||||||
|
|
||||||
v := TransportTCP
|
v := TransportTCP
|
||||||
c.effectiveTransport = &v
|
c.setuppedTransport = &v
|
||||||
c.effectiveSecure = th.Secure
|
c.setuppedProfile = th.Profile
|
||||||
|
|
||||||
// some Hikvision cameras require a describe before a setup
|
// some Hikvision cameras require a describe before a setup
|
||||||
_, _, err = c.doDescribe(c.lastDescribeURL)
|
_, _, err = c.doDescribe(c.lastDescribeURL)
|
||||||
@@ -1906,12 +1918,12 @@ func (c *Client) doSetup(
|
|||||||
cm.tcpChannel = thRes.InterleavedIDs[0]
|
cm.tcpChannel = thRes.InterleavedIDs[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if cm.secure {
|
if thRes.Profile != th.Profile {
|
||||||
if !thRes.Secure {
|
cm.close()
|
||||||
cm.close()
|
return nil, fmt.Errorf("returned profile does not match requested profile")
|
||||||
return nil, fmt.Errorf("transport was not setupped securely")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
if cm.secure {
|
||||||
var mikeyMsg *mikey.Message
|
var mikeyMsg *mikey.Message
|
||||||
|
|
||||||
// extract key-mgmt from (in order of priority):
|
// extract key-mgmt from (in order of priority):
|
||||||
@@ -1943,9 +1955,6 @@ func (c *Client) doSetup(
|
|||||||
cm.close()
|
cm.close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else if thRes.Secure {
|
|
||||||
cm.close()
|
|
||||||
return nil, fmt.Errorf("received unexpected secure profile")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.setuppedMedias == nil {
|
if c.setuppedMedias == nil {
|
||||||
@@ -1955,8 +1964,8 @@ func (c *Client) doSetup(
|
|||||||
c.setuppedMedias[medi] = cm
|
c.setuppedMedias[medi] = cm
|
||||||
|
|
||||||
c.baseURL = baseURL
|
c.baseURL = baseURL
|
||||||
c.effectiveTransport = &transport
|
c.setuppedTransport = &transport
|
||||||
c.effectiveSecure = th.Secure
|
c.setuppedProfile = th.Profile
|
||||||
|
|
||||||
if medi.IsBackChannel {
|
if medi.IsBackChannel {
|
||||||
c.backChannelSetupped = true
|
c.backChannelSetupped = true
|
||||||
@@ -2057,7 +2066,7 @@ func (c *Client) doPlay(ra *headers.Range) (*base.Response, error) {
|
|||||||
// when protocol is UDP,
|
// when protocol is UDP,
|
||||||
// open the firewall by sending empty packets to the remote part.
|
// open the firewall by sending empty packets to the remote part.
|
||||||
// do this before sending the PLAY request.
|
// do this before sending the PLAY request.
|
||||||
if *c.effectiveTransport == TransportUDP {
|
if *c.setuppedTransport == TransportUDP {
|
||||||
for _, cm := range c.setuppedMedias {
|
for _, cm := range c.setuppedMedias {
|
||||||
if !cm.media.IsBackChannel && cm.udpRTPListener.writeAddr != nil {
|
if !cm.media.IsBackChannel && cm.udpRTPListener.writeAddr != nil {
|
||||||
buf, _ := (&rtp.Packet{Header: rtp.Header{Version: 2}}).Marshal()
|
buf, _ := (&rtp.Packet{Header: rtp.Header{Version: 2}}).Marshal()
|
||||||
|
@@ -399,11 +399,11 @@ func TestClientPlay(t *testing.T) {
|
|||||||
h := base.Header{}
|
h := base.Header{}
|
||||||
|
|
||||||
th := headers.Transport{
|
th := headers.Transport{
|
||||||
Secure: inTH.Secure,
|
Profile: inTH.Profile,
|
||||||
}
|
}
|
||||||
|
|
||||||
if ca.secure == "secure" {
|
if ca.secure == "secure" {
|
||||||
require.True(t, inTH.Secure)
|
require.Equal(t, headers.TransportProfileSAVP, inTH.Profile)
|
||||||
|
|
||||||
var keyMgmt headers.KeyMgmt
|
var keyMgmt headers.KeyMgmt
|
||||||
err2 = keyMgmt.Unmarshal(req.Header["KeyMgmt"])
|
err2 = keyMgmt.Unmarshal(req.Header["KeyMgmt"])
|
||||||
|
@@ -213,7 +213,7 @@ func TestClientRecord(t *testing.T) {
|
|||||||
require.NoError(t, err2)
|
require.NoError(t, err2)
|
||||||
|
|
||||||
if ca.secure == "secure" {
|
if ca.secure == "secure" {
|
||||||
require.True(t, desc2.Medias[0].Secure)
|
require.Equal(t, headers.TransportProfileSAVP, desc2.Medias[0].Profile)
|
||||||
|
|
||||||
_, err = mikeyToContext(desc2.Medias[0].KeyMgmtMikey)
|
_, err = mikeyToContext(desc2.Medias[0].KeyMgmtMikey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -257,16 +257,14 @@ func TestClientRecord(t *testing.T) {
|
|||||||
|
|
||||||
th := headers.Transport{
|
th := headers.Transport{
|
||||||
Delivery: deliveryPtr(headers.TransportDeliveryUnicast),
|
Delivery: deliveryPtr(headers.TransportDeliveryUnicast),
|
||||||
Secure: inTH.Secure,
|
Profile: inTH.Profile,
|
||||||
}
|
}
|
||||||
|
|
||||||
var srtpInCtx *wrappedSRTPContext
|
var srtpInCtx *wrappedSRTPContext
|
||||||
var srtpOutCtx *wrappedSRTPContext
|
var srtpOutCtx *wrappedSRTPContext
|
||||||
|
|
||||||
if ca.secure == "secure" {
|
if ca.secure == "secure" {
|
||||||
th.Secure = true
|
require.Equal(t, inTH.Profile, headers.TransportProfileSAVP)
|
||||||
|
|
||||||
require.True(t, th.Secure)
|
|
||||||
|
|
||||||
var keyMgmt headers.KeyMgmt
|
var keyMgmt headers.KeyMgmt
|
||||||
err = keyMgmt.Unmarshal(req.Header["KeyMgmt"])
|
err = keyMgmt.Unmarshal(req.Header["KeyMgmt"])
|
||||||
|
@@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/mikey"
|
"github.com/bluenviron/gortsplib/v4/pkg/mikey"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,15 +79,20 @@ type Media struct {
|
|||||||
// Whether this media is a back channel.
|
// Whether this media is a back channel.
|
||||||
IsBackChannel bool
|
IsBackChannel bool
|
||||||
|
|
||||||
// Control attribute.
|
|
||||||
Control string
|
|
||||||
|
|
||||||
// Whether the transport is secure.
|
// Whether the transport is secure.
|
||||||
|
//
|
||||||
|
// Deprecated: replaced by Profile
|
||||||
Secure bool
|
Secure bool
|
||||||
|
|
||||||
|
// RTP Profile.
|
||||||
|
Profile headers.TransportProfile
|
||||||
|
|
||||||
// key-mgmt attribute.
|
// key-mgmt attribute.
|
||||||
KeyMgmtMikey *mikey.Message
|
KeyMgmtMikey *mikey.Message
|
||||||
|
|
||||||
|
// Control attribute.
|
||||||
|
Control string
|
||||||
|
|
||||||
// Formats contained into the media.
|
// Formats contained into the media.
|
||||||
Formats []format.Format
|
Formats []format.Format
|
||||||
}
|
}
|
||||||
@@ -101,8 +107,14 @@ func (m *Media) Unmarshal(md *psdp.MediaDescription) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.IsBackChannel = isBackChannel(md.Attributes)
|
m.IsBackChannel = isBackChannel(md.Attributes)
|
||||||
m.Control = getAttribute(md.Attributes, "control")
|
|
||||||
m.Secure = slices.Contains(md.MediaName.Protos, "SAVP")
|
if slices.Contains(md.MediaName.Protos, "SAVP") {
|
||||||
|
m.Secure = true
|
||||||
|
m.Profile = headers.TransportProfileSAVP
|
||||||
|
} else {
|
||||||
|
m.Secure = false
|
||||||
|
m.Profile = headers.TransportProfileAVP
|
||||||
|
}
|
||||||
|
|
||||||
if enc := getAttribute(md.Attributes, "key-mgmt"); enc != "" {
|
if enc := getAttribute(md.Attributes, "key-mgmt"); enc != "" {
|
||||||
if !strings.HasPrefix(enc, "mikey ") {
|
if !strings.HasPrefix(enc, "mikey ") {
|
||||||
@@ -121,6 +133,8 @@ func (m *Media) Unmarshal(md *psdp.MediaDescription) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.Control = getAttribute(md.Attributes, "control")
|
||||||
|
|
||||||
m.Formats = nil
|
m.Formats = nil
|
||||||
|
|
||||||
for _, payloadType := range md.MediaName.Formats {
|
for _, payloadType := range md.MediaName.Formats {
|
||||||
@@ -152,11 +166,16 @@ func (m Media) Marshal() *psdp.MediaDescription {
|
|||||||
|
|
||||||
// Marshal2 encodes the media in SDP format.
|
// Marshal2 encodes the media in SDP format.
|
||||||
func (m Media) Marshal2() (*psdp.MediaDescription, error) {
|
func (m Media) Marshal2() (*psdp.MediaDescription, error) {
|
||||||
|
if m.Secure {
|
||||||
|
m.Profile = headers.TransportProfileSAVP
|
||||||
|
}
|
||||||
|
|
||||||
var protos []string
|
var protos []string
|
||||||
if !m.Secure {
|
|
||||||
protos = []string{"RTP", "AVP"}
|
if m.Profile == headers.TransportProfileSAVP {
|
||||||
} else {
|
|
||||||
protos = []string{"RTP", "SAVP"}
|
protos = []string{"RTP", "SAVP"}
|
||||||
|
} else {
|
||||||
|
protos = []string{"RTP", "AVP"}
|
||||||
}
|
}
|
||||||
|
|
||||||
md := &psdp.MediaDescription{
|
md := &psdp.MediaDescription{
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/mikey"
|
"github.com/bluenviron/gortsplib/v4/pkg/mikey"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/sdp"
|
"github.com/bluenviron/gortsplib/v4/pkg/sdp"
|
||||||
)
|
)
|
||||||
@@ -726,6 +727,7 @@ var casesSession = []struct {
|
|||||||
Type: "video",
|
Type: "video",
|
||||||
Control: "trackID=0",
|
Control: "trackID=0",
|
||||||
Secure: true,
|
Secure: true,
|
||||||
|
Profile: headers.TransportProfileSAVP,
|
||||||
Formats: []format.Format{&format.H264{
|
Formats: []format.Format{&format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
}},
|
}},
|
||||||
@@ -762,6 +764,7 @@ var casesSession = []struct {
|
|||||||
Type: "video",
|
Type: "video",
|
||||||
Control: "trackID=0",
|
Control: "trackID=0",
|
||||||
Secure: true,
|
Secure: true,
|
||||||
|
Profile: headers.TransportProfileSAVP,
|
||||||
KeyMgmtMikey: &mikey.Message{ //nolint:dupl
|
KeyMgmtMikey: &mikey.Message{ //nolint:dupl
|
||||||
Header: mikey.Header{
|
Header: mikey.Header{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
|
@@ -38,6 +38,15 @@ func parsePorts(val string) (*[2]int, error) {
|
|||||||
return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val)
|
return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TransportProfile is a transport profile.
|
||||||
|
type TransportProfile int
|
||||||
|
|
||||||
|
// transport profiles.
|
||||||
|
const (
|
||||||
|
TransportProfileAVP TransportProfile = iota
|
||||||
|
TransportProfileSAVP
|
||||||
|
)
|
||||||
|
|
||||||
// TransportProtocol is a transport protocol.
|
// TransportProtocol is a transport protocol.
|
||||||
type TransportProtocol int
|
type TransportProtocol int
|
||||||
|
|
||||||
@@ -116,13 +125,18 @@ func (m TransportMode) String() string {
|
|||||||
|
|
||||||
// Transport is a Transport header.
|
// Transport is a Transport header.
|
||||||
type Transport struct {
|
type Transport struct {
|
||||||
// protocol of the stream.
|
|
||||||
Protocol TransportProtocol
|
|
||||||
|
|
||||||
// Whether the secure variant is active.
|
// Whether the secure variant is active.
|
||||||
|
//
|
||||||
|
// Deprecated: replaced by Profile.
|
||||||
Secure bool
|
Secure bool
|
||||||
|
|
||||||
// (optional) delivery method of the stream.
|
// profile.
|
||||||
|
Profile TransportProfile
|
||||||
|
|
||||||
|
// protocol.
|
||||||
|
Protocol TransportProtocol
|
||||||
|
|
||||||
|
// (optional) delivery method.
|
||||||
Delivery *TransportDelivery
|
Delivery *TransportDelivery
|
||||||
|
|
||||||
// (optional) Source IP.
|
// (optional) Source IP.
|
||||||
@@ -146,7 +160,7 @@ type Transport struct {
|
|||||||
// (optional) server ports.
|
// (optional) server ports.
|
||||||
ServerPorts *[2]int
|
ServerPorts *[2]int
|
||||||
|
|
||||||
// (optional) SSRC of the packets of the stream.
|
// (optional) SSRC of packets.
|
||||||
SSRC *uint32
|
SSRC *uint32
|
||||||
|
|
||||||
// (optional) mode.
|
// (optional) mode.
|
||||||
@@ -175,21 +189,25 @@ func (h *Transport) Unmarshal(v base.HeaderValue) error {
|
|||||||
|
|
||||||
switch k {
|
switch k {
|
||||||
case "RTP/AVP", "RTP/AVP/UDP":
|
case "RTP/AVP", "RTP/AVP/UDP":
|
||||||
|
h.Profile = TransportProfileAVP
|
||||||
h.Protocol = TransportProtocolUDP
|
h.Protocol = TransportProtocolUDP
|
||||||
profileFound = true
|
profileFound = true
|
||||||
|
|
||||||
case "RTP/AVP/TCP":
|
case "RTP/AVP/TCP":
|
||||||
|
h.Profile = TransportProfileAVP
|
||||||
h.Protocol = TransportProtocolTCP
|
h.Protocol = TransportProtocolTCP
|
||||||
profileFound = true
|
profileFound = true
|
||||||
|
|
||||||
case "RTP/SAVP", "RTP/SAVP/UDP":
|
case "RTP/SAVP", "RTP/SAVP/UDP":
|
||||||
h.Protocol = TransportProtocolUDP
|
h.Protocol = TransportProtocolUDP
|
||||||
h.Secure = true
|
h.Secure = true
|
||||||
|
h.Profile = TransportProfileSAVP
|
||||||
profileFound = true
|
profileFound = true
|
||||||
|
|
||||||
case "RTP/SAVP/TCP":
|
case "RTP/SAVP/TCP":
|
||||||
h.Protocol = TransportProtocolTCP
|
|
||||||
h.Secure = true
|
h.Secure = true
|
||||||
|
h.Profile = TransportProfileSAVP
|
||||||
|
h.Protocol = TransportProtocolTCP
|
||||||
profileFound = true
|
profileFound = true
|
||||||
|
|
||||||
case "unicast":
|
case "unicast":
|
||||||
@@ -299,16 +317,20 @@ func (h *Transport) Unmarshal(v base.HeaderValue) error {
|
|||||||
func (h Transport) Marshal() base.HeaderValue {
|
func (h Transport) Marshal() base.HeaderValue {
|
||||||
var rets []string
|
var rets []string
|
||||||
|
|
||||||
|
if h.Secure {
|
||||||
|
h.Profile = TransportProfileSAVP
|
||||||
|
}
|
||||||
|
|
||||||
var profile string
|
var profile string
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case h.Protocol == TransportProtocolUDP && !h.Secure:
|
case h.Protocol == TransportProtocolUDP && h.Profile == TransportProfileAVP:
|
||||||
profile = "RTP/AVP"
|
profile = "RTP/AVP"
|
||||||
case h.Protocol == TransportProtocolTCP && !h.Secure:
|
case h.Protocol == TransportProtocolTCP && h.Profile == TransportProfileAVP:
|
||||||
profile = "RTP/AVP/TCP"
|
profile = "RTP/AVP/TCP"
|
||||||
case h.Protocol == TransportProtocolUDP && h.Secure:
|
case h.Protocol == TransportProtocolUDP && h.Profile == TransportProfileSAVP:
|
||||||
profile = "RTP/SAVP"
|
profile = "RTP/SAVP"
|
||||||
case h.Protocol == TransportProtocolTCP && h.Secure:
|
case h.Protocol == TransportProtocolTCP && h.Profile == TransportProfileSAVP:
|
||||||
profile = "RTP/SAVP/TCP"
|
profile = "RTP/SAVP/TCP"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -175,6 +175,7 @@ var casesTransport = []struct {
|
|||||||
Transport{
|
Transport{
|
||||||
Protocol: TransportProtocolUDP,
|
Protocol: TransportProtocolUDP,
|
||||||
Secure: true,
|
Secure: true,
|
||||||
|
Profile: TransportProfileSAVP,
|
||||||
Delivery: deliveryPtr(TransportDeliveryUnicast),
|
Delivery: deliveryPtr(TransportDeliveryUnicast),
|
||||||
ClientPorts: &[2]int{3456, 3457},
|
ClientPorts: &[2]int{3456, 3457},
|
||||||
Mode: transportModePtr(TransportModePlay),
|
Mode: transportModePtr(TransportModePlay),
|
||||||
@@ -187,6 +188,7 @@ var casesTransport = []struct {
|
|||||||
Transport{
|
Transport{
|
||||||
Protocol: TransportProtocolTCP,
|
Protocol: TransportProtocolTCP,
|
||||||
Secure: true,
|
Secure: true,
|
||||||
|
Profile: TransportProfileSAVP,
|
||||||
InterleavedIDs: &[2]int{0, 1},
|
InterleavedIDs: &[2]int{0, 1},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -777,7 +777,7 @@ func TestServerPlay(t *testing.T) {
|
|||||||
desc := doDescribe(t, conn, false)
|
desc := doDescribe(t, conn, false)
|
||||||
|
|
||||||
if ca.secure == "secure" {
|
if ca.secure == "secure" {
|
||||||
require.True(t, desc.Medias[0].Secure)
|
require.Equal(t, headers.TransportProfileSAVP, desc.Medias[0].Profile)
|
||||||
require.NotEmpty(t, desc.Medias[0].KeyMgmtMikey)
|
require.NotEmpty(t, desc.Medias[0].KeyMgmtMikey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -811,7 +811,7 @@ func TestServerPlay(t *testing.T) {
|
|||||||
var srtpOutCtx *wrappedSRTPContext
|
var srtpOutCtx *wrappedSRTPContext
|
||||||
|
|
||||||
if ca.secure == "secure" {
|
if ca.secure == "secure" {
|
||||||
inTH.Secure = true
|
inTH.Profile = headers.TransportProfileSAVP
|
||||||
|
|
||||||
key := make([]byte, srtpKeyLength)
|
key := make([]byte, srtpKeyLength)
|
||||||
_, err = rand.Read(key)
|
_, err = rand.Read(key)
|
||||||
@@ -854,7 +854,7 @@ func TestServerPlay(t *testing.T) {
|
|||||||
var srtpInCtx *wrappedSRTPContext
|
var srtpInCtx *wrappedSRTPContext
|
||||||
|
|
||||||
if ca.secure == "secure" {
|
if ca.secure == "secure" {
|
||||||
require.True(t, th.Secure)
|
require.Equal(t, headers.TransportProfileSAVP, th.Profile)
|
||||||
|
|
||||||
var keyMgmt headers.KeyMgmt
|
var keyMgmt headers.KeyMgmt
|
||||||
err = keyMgmt.Unmarshal(res.Header["KeyMgmt"])
|
err = keyMgmt.Unmarshal(res.Header["KeyMgmt"])
|
||||||
|
@@ -752,7 +752,7 @@ func TestServerRecord(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ca.secure == "secure" {
|
if ca.secure == "secure" {
|
||||||
inTH.Secure = true
|
inTH.Profile = headers.TransportProfileSAVP
|
||||||
|
|
||||||
key := make([]byte, srtpKeyLength)
|
key := make([]byte, srtpKeyLength)
|
||||||
_, err = rand.Read(key)
|
_, err = rand.Read(key)
|
||||||
@@ -800,7 +800,7 @@ func TestServerRecord(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ca.secure == "secure" {
|
if ca.secure == "secure" {
|
||||||
require.True(t, th.Secure)
|
require.Equal(t, headers.TransportProfileSAVP, th.Profile)
|
||||||
|
|
||||||
var keyMgmt headers.KeyMgmt
|
var keyMgmt headers.KeyMgmt
|
||||||
err = keyMgmt.Unmarshal(res.Header["KeyMgmt"])
|
err = keyMgmt.Unmarshal(res.Header["KeyMgmt"])
|
||||||
|
@@ -31,6 +31,10 @@ import (
|
|||||||
|
|
||||||
type readFunc func([]byte) bool
|
type readFunc func([]byte) bool
|
||||||
|
|
||||||
|
func isSecure(profile headers.TransportProfile) bool {
|
||||||
|
return profile == headers.TransportProfileSAVP
|
||||||
|
}
|
||||||
|
|
||||||
func stringsReverseIndex(s, substr string) int {
|
func stringsReverseIndex(s, substr string) int {
|
||||||
for i := len(s) - 1 - len(substr); i >= 0; i-- {
|
for i := len(s) - 1 - len(substr); i >= 0; i-- {
|
||||||
if s[i:i+len(substr)] == substr {
|
if s[i:i+len(substr)] == substr {
|
||||||
@@ -181,12 +185,12 @@ func isTransportSupported(s *Server, tr *headers.Transport) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prevent using unsecure UDP with RTSPS
|
// prevent using unsecure UDP with RTSPS
|
||||||
if tr.Protocol == headers.TransportProtocolUDP && !tr.Secure && s.TLSConfig != nil {
|
if tr.Protocol == headers.TransportProtocolUDP && !isSecure(tr.Profile) && s.TLSConfig != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// prevent using secure profiles with plain RTSP, since keys are in plain
|
// prevent using secure profiles with plain RTSP, since keys are in plain
|
||||||
if tr.Secure && s.TLSConfig == nil {
|
if isSecure(tr.Profile) && s.TLSConfig == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,7 +424,7 @@ type ServerSession struct {
|
|||||||
setuppedMediasOrdered []*serverSessionMedia
|
setuppedMediasOrdered []*serverSessionMedia
|
||||||
tcpCallbackByChannel map[int]readFunc
|
tcpCallbackByChannel map[int]readFunc
|
||||||
setuppedTransport *Transport
|
setuppedTransport *Transport
|
||||||
setuppedSecure bool
|
setuppedProfile headers.TransportProfile
|
||||||
setuppedStream *ServerStream // play
|
setuppedStream *ServerStream // play
|
||||||
setuppedPath string
|
setuppedPath string
|
||||||
setuppedQuery string
|
setuppedQuery string
|
||||||
@@ -503,7 +507,7 @@ func (ss *ServerSession) SetuppedTransport() *Transport {
|
|||||||
// If this is false, it does not mean that the stream is not secure, since
|
// If this is false, it does not mean that the stream is not secure, since
|
||||||
// there are some combinations that are secure nonetheless, like RTSPS+TCP+unsecure.
|
// there are some combinations that are secure nonetheless, like RTSPS+TCP+unsecure.
|
||||||
func (ss *ServerSession) SetuppedSecure() bool {
|
func (ss *ServerSession) SetuppedSecure() bool {
|
||||||
return ss.setuppedSecure
|
return isSecure(ss.setuppedProfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetuppedStream returns the stream associated with the session.
|
// SetuppedStream returns the stream associated with the session.
|
||||||
@@ -1126,7 +1130,7 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
|
|||||||
|
|
||||||
var srtpInCtx *wrappedSRTPContext
|
var srtpInCtx *wrappedSRTPContext
|
||||||
|
|
||||||
if inTH.Secure {
|
if isSecure(inTH.Profile) {
|
||||||
var keyMgmt headers.KeyMgmt
|
var keyMgmt headers.KeyMgmt
|
||||||
err = keyMgmt.Unmarshal(req.Header["KeyMgmt"])
|
err = keyMgmt.Unmarshal(req.Header["KeyMgmt"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1143,7 +1147,7 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ss.setuppedTransport != nil && (*ss.setuppedTransport != transport || ss.setuppedSecure != inTH.Secure) {
|
if ss.setuppedTransport != nil && (*ss.setuppedTransport != transport || ss.setuppedProfile != inTH.Profile) {
|
||||||
return &base.Response{
|
return &base.Response{
|
||||||
StatusCode: base.StatusBadRequest,
|
StatusCode: base.StatusBadRequest,
|
||||||
}, liberrors.ErrServerMediasDifferentTransports{}
|
}, liberrors.ErrServerMediasDifferentTransports{}
|
||||||
@@ -1247,7 +1251,7 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
ss.setuppedTransport = &transport
|
ss.setuppedTransport = &transport
|
||||||
ss.setuppedSecure = inTH.Secure
|
ss.setuppedProfile = inTH.Profile
|
||||||
|
|
||||||
if ss.state == ServerSessionStateInitial {
|
if ss.state == ServerSessionStateInitial {
|
||||||
err = stream.readerAdd(ss,
|
err = stream.readerAdd(ss,
|
||||||
@@ -1266,7 +1270,7 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
th := headers.Transport{
|
th := headers.Transport{
|
||||||
Secure: inTH.Secure,
|
Profile: inTH.Profile,
|
||||||
}
|
}
|
||||||
|
|
||||||
if ss.state == ServerSessionStatePrePlay {
|
if ss.state == ServerSessionStatePrePlay {
|
||||||
@@ -1355,7 +1359,7 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
|
|||||||
|
|
||||||
res.Header["Transport"] = th.Marshal()
|
res.Header["Transport"] = th.Marshal()
|
||||||
|
|
||||||
if inTH.Secure {
|
if isSecure(inTH.Profile) {
|
||||||
ssrcs := make([]uint32, len(sm.formats))
|
ssrcs := make([]uint32, len(sm.formats))
|
||||||
n := 0
|
n := 0
|
||||||
for _, sf := range sm.formats {
|
for _, sf := range sm.formats {
|
||||||
|
@@ -159,7 +159,7 @@ func (sf *serverSessionFormat) writePacketRTP(pkt *rtp.Packet) error {
|
|||||||
pkt.SSRC = sf.localSSRC
|
pkt.SSRC = sf.localSSRC
|
||||||
|
|
||||||
maxPlainPacketSize := sf.sm.ss.s.MaxPacketSize
|
maxPlainPacketSize := sf.sm.ss.s.MaxPacketSize
|
||||||
if sf.sm.ss.setuppedSecure {
|
if isSecure(sf.sm.ss.setuppedProfile) {
|
||||||
maxPlainPacketSize -= srtpOverhead
|
maxPlainPacketSize -= srtpOverhead
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ func (sf *serverSessionFormat) writePacketRTP(pkt *rtp.Packet) error {
|
|||||||
plain = plain[:n]
|
plain = plain[:n]
|
||||||
|
|
||||||
var encr []byte
|
var encr []byte
|
||||||
if sf.sm.ss.setuppedSecure {
|
if isSecure(sf.sm.ss.setuppedProfile) {
|
||||||
encr = make([]byte, sf.sm.ss.s.MaxPacketSize)
|
encr = make([]byte, sf.sm.ss.s.MaxPacketSize)
|
||||||
encr, err = sf.sm.srtpOutCtx.encryptRTP(encr, plain, &pkt.Header)
|
encr, err = sf.sm.srtpOutCtx.encryptRTP(encr, plain, &pkt.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -179,7 +179,7 @@ func (sf *serverSessionFormat) writePacketRTP(pkt *rtp.Packet) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if sf.sm.ss.setuppedSecure {
|
if isSecure(sf.sm.ss.setuppedProfile) {
|
||||||
return sf.writePacketRTPEncoded(encr)
|
return sf.writePacketRTPEncoded(encr)
|
||||||
}
|
}
|
||||||
return sf.writePacketRTPEncoded(plain)
|
return sf.writePacketRTPEncoded(plain)
|
||||||
|
@@ -459,7 +459,7 @@ func (sm *serverSessionMedia) writePacketRTCP(pkt rtcp.Packet) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
maxPlainPacketSize := sm.ss.s.MaxPacketSize
|
maxPlainPacketSize := sm.ss.s.MaxPacketSize
|
||||||
if sm.ss.setuppedSecure {
|
if isSecure(sm.ss.setuppedProfile) {
|
||||||
maxPlainPacketSize -= srtcpOverhead
|
maxPlainPacketSize -= srtcpOverhead
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,7 +468,7 @@ func (sm *serverSessionMedia) writePacketRTCP(pkt rtcp.Packet) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var encr []byte
|
var encr []byte
|
||||||
if sm.ss.setuppedSecure {
|
if isSecure(sm.ss.setuppedProfile) {
|
||||||
encr = make([]byte, sm.ss.s.MaxPacketSize)
|
encr = make([]byte, sm.ss.s.MaxPacketSize)
|
||||||
encr, err = sm.srtpOutCtx.encryptRTCP(encr, plain, nil)
|
encr, err = sm.srtpOutCtx.encryptRTCP(encr, plain, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -476,7 +476,7 @@ func (sm *serverSessionMedia) writePacketRTCP(pkt rtcp.Packet) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if sm.ss.setuppedSecure {
|
if isSecure(sm.ss.setuppedProfile) {
|
||||||
return sm.writePacketRTCPEncoded(encr)
|
return sm.writePacketRTCPEncoded(encr)
|
||||||
}
|
}
|
||||||
return sm.writePacketRTCPEncoded(plain)
|
return sm.writePacketRTCPEncoded(plain)
|
||||||
|
@@ -120,7 +120,7 @@ func (sf *serverStreamFormat) writePacketRTP(pkt *rtp.Packet, ntp time.Time) err
|
|||||||
if rsm, ok := r.setuppedMedias[sf.sm.media]; ok {
|
if rsm, ok := r.setuppedMedias[sf.sm.media]; ok {
|
||||||
rsf := rsm.formats[pkt.PayloadType]
|
rsf := rsm.formats[pkt.PayloadType]
|
||||||
|
|
||||||
if r.setuppedSecure {
|
if isSecure(r.setuppedProfile) {
|
||||||
err = rsf.writePacketRTPEncoded(encr)
|
err = rsf.writePacketRTPEncoded(encr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.onStreamWriteError(err)
|
r.onStreamWriteError(err)
|
||||||
|
@@ -110,7 +110,7 @@ func (sm *serverStreamMedia) writePacketRTCP(pkt rtcp.Packet) error {
|
|||||||
// send unicast
|
// send unicast
|
||||||
for r := range sm.st.activeUnicastReaders {
|
for r := range sm.st.activeUnicastReaders {
|
||||||
if sm, ok := r.setuppedMedias[sm.media]; ok {
|
if sm, ok := r.setuppedMedias[sm.media]; ok {
|
||||||
if r.setuppedSecure {
|
if isSecure(r.setuppedProfile) {
|
||||||
err = sm.writePacketRTCPEncoded(encr)
|
err = sm.writePacketRTCPEncoded(encr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.onStreamWriteError(err)
|
r.onStreamWriteError(err)
|
||||||
|
Reference in New Issue
Block a user