Files
gortsplib/pkg/headers/transport.go

378 lines
7.5 KiB
Go

package headers
import (
"encoding/hex"
"fmt"
"net"
"strconv"
"strings"
"github.com/bluenviron/gortsplib/v4/pkg/base"
)
func parsePorts(val string) (*[2]int, error) {
ports := strings.Split(val, "-")
if len(ports) == 2 {
port1, err := strconv.ParseUint(ports[0], 10, 31)
if err != nil {
return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val)
}
port2, err := strconv.ParseUint(ports[1], 10, 31)
if err != nil {
return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val)
}
return &[2]int{int(port1), int(port2)}, nil
}
if len(ports) == 1 {
port1, err := strconv.ParseUint(ports[0], 10, 31)
if err != nil {
return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val)
}
return &[2]int{int(port1), int(port1 + 1)}, nil
}
return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val)
}
// TransportProtocol is a transport protocol.
type TransportProtocol int
// transport protocols.
const (
TransportProtocolUDP TransportProtocol = iota
TransportProtocolTCP
)
// String implements fmt.Stringer.
//
// Deprecated: not used anymore.
func (p TransportProtocol) String() string {
if p == TransportProtocolUDP {
return "RTP/AVP"
}
return "RTP/AVP/TCP"
}
// TransportDelivery is a delivery method.
type TransportDelivery int
// transport delivery methods.
const (
TransportDeliveryUnicast TransportDelivery = iota
TransportDeliveryMulticast
)
// String implements fmt.Stringer.
//
// Deprecated: not used anymore.
func (d TransportDelivery) String() string {
if d == TransportDeliveryUnicast {
return "unicast"
}
return "multicast"
}
// TransportMode is a transport mode.
type TransportMode int
const (
// TransportModePlay is the "play" transport mode
TransportModePlay TransportMode = iota
// TransportModeRecord is the "record" transport mode
TransportModeRecord
)
func (m *TransportMode) unmarshal(v string) error {
str := strings.ToLower(v)
switch str {
case "play":
*m = TransportModePlay
return nil
// receive is an old alias for record, used by ffmpeg with the
// -listen flag, and by Darwin Streaming Server
case "record", "receive":
*m = TransportModeRecord
return nil
default:
return fmt.Errorf("invalid transport mode: '%s'", str)
}
}
// String implements fmt.Stringer.
func (m TransportMode) String() string {
if m == TransportModePlay {
return "play"
}
return "record"
}
// Transport is a Transport header.
type Transport struct {
// protocol of the stream.
Protocol TransportProtocol
// Whether the secure variant is active.
Secure bool
// (optional) delivery method of the stream.
Delivery *TransportDelivery
// (optional) Source IP.
Source *net.IP
// (optional) destination IP.
Destination *net.IP
// (optional) interleaved frame IDs.
InterleavedIDs *[2]int
// (optional) TTL.
TTL *uint
// (optional) ports.
Ports *[2]int
// (optional) client ports.
ClientPorts *[2]int
// (optional) server ports.
ServerPorts *[2]int
// (optional) SSRC of the packets of the stream.
SSRC *uint32
// (optional) mode.
Mode *TransportMode
}
// Unmarshal decodes a Transport header.
func (h *Transport) Unmarshal(v base.HeaderValue) error {
if len(v) == 0 {
return fmt.Errorf("value not provided")
}
if len(v) > 1 {
return fmt.Errorf("value provided multiple times (%v)", v)
}
kvs, err := keyValParse(v[0], ';')
if err != nil {
return err
}
profileFound := false
for k, rv := range kvs {
v := rv
switch k {
case "RTP/AVP", "RTP/AVP/UDP":
h.Protocol = TransportProtocolUDP
profileFound = true
case "RTP/AVP/TCP":
h.Protocol = TransportProtocolTCP
profileFound = true
case "RTP/SAVP", "RTP/SAVP/UDP":
h.Protocol = TransportProtocolUDP
h.Secure = true
profileFound = true
case "RTP/SAVP/TCP":
h.Protocol = TransportProtocolTCP
h.Secure = true
profileFound = true
case "unicast":
v := TransportDeliveryUnicast
h.Delivery = &v
case "multicast":
v := TransportDeliveryMulticast
h.Delivery = &v
case "source":
if v != "" {
ip := net.ParseIP(v)
if ip == nil {
addrs, err2 := net.LookupHost(v)
if err2 != nil {
return fmt.Errorf("invalid source (%v)", v)
}
ip = net.ParseIP(addrs[0])
if ip == nil {
return fmt.Errorf("invalid source (%v)", v)
}
}
h.Source = &ip
}
case "destination":
if v != "" {
ip := net.ParseIP(v)
if ip == nil {
return fmt.Errorf("invalid destination (%v)", v)
}
h.Destination = &ip
}
case "interleaved":
ports, err2 := parsePorts(v)
if err2 != nil {
return err2
}
h.InterleavedIDs = ports
case "ttl":
tmp, err2 := strconv.ParseUint(v, 10, 32)
if err2 != nil {
return err2
}
vu := uint(tmp)
h.TTL = &vu
case "port":
ports, err2 := parsePorts(v)
if err2 != nil {
return err2
}
h.Ports = ports
case "client_port":
ports, err2 := parsePorts(v)
if err2 != nil {
return err2
}
h.ClientPorts = ports
case "server_port":
ports, err2 := parsePorts(v)
if err2 != nil {
return err2
}
h.ServerPorts = ports
case "ssrc":
v = strings.TrimLeft(v, " ")
if (len(v) % 2) != 0 {
v = "0" + v
}
if tmp, err2 := hex.DecodeString(v); err2 == nil && len(tmp) <= 4 {
var ssrc [4]byte
copy(ssrc[4-len(tmp):], tmp)
v := uint32(ssrc[0])<<24 | uint32(ssrc[1])<<16 | uint32(ssrc[2])<<8 | uint32(ssrc[3])
h.SSRC = &v
}
case "mode":
var m TransportMode
err = m.unmarshal(v)
if err != nil {
return err
}
h.Mode = &m
default:
// ignore non-standard keys
}
}
if !profileFound {
return fmt.Errorf("profile is missing: %v", v[0])
}
return nil
}
// Marshal encodes a Transport header.
func (h Transport) Marshal() base.HeaderValue {
var rets []string
var profile string
switch {
case h.Protocol == TransportProtocolUDP && !h.Secure:
profile = "RTP/AVP"
case h.Protocol == TransportProtocolTCP && !h.Secure:
profile = "RTP/AVP/TCP"
case h.Protocol == TransportProtocolUDP && h.Secure:
profile = "RTP/SAVP"
case h.Protocol == TransportProtocolTCP && h.Secure:
profile = "RTP/SAVP/TCP"
}
rets = append(rets, profile)
if h.Delivery != nil {
var delivery string
switch *h.Delivery {
case TransportDeliveryUnicast:
delivery = "unicast"
case TransportDeliveryMulticast:
delivery = "multicast"
}
rets = append(rets, delivery)
}
if h.Source != nil {
rets = append(rets, "source="+h.Source.String())
}
if h.Destination != nil {
rets = append(rets, "destination="+h.Destination.String())
}
if h.InterleavedIDs != nil {
rets = append(rets, "interleaved="+strconv.FormatInt(int64(h.InterleavedIDs[0]), 10)+
"-"+strconv.FormatInt(int64(h.InterleavedIDs[1]), 10))
}
if h.Ports != nil {
rets = append(rets, "port="+strconv.FormatInt(int64(h.Ports[0]), 10)+
"-"+strconv.FormatInt(int64(h.Ports[1]), 10))
}
if h.TTL != nil {
rets = append(rets, "ttl="+strconv.FormatUint(uint64(*h.TTL), 10))
}
if h.ClientPorts != nil {
rets = append(rets, "client_port="+strconv.FormatInt(int64(h.ClientPorts[0]), 10)+
"-"+strconv.FormatInt(int64(h.ClientPorts[1]), 10))
}
if h.ServerPorts != nil {
rets = append(rets, "server_port="+strconv.FormatInt(int64(h.ServerPorts[0]), 10)+
"-"+strconv.FormatInt(int64(h.ServerPorts[1]), 10))
}
if h.SSRC != nil {
tmp := make([]byte, 4)
tmp[0] = byte(*h.SSRC >> 24)
tmp[1] = byte(*h.SSRC >> 16)
tmp[2] = byte(*h.SSRC >> 8)
tmp[3] = byte(*h.SSRC)
rets = append(rets, "ssrc="+strings.ToUpper(hex.EncodeToString(tmp)))
}
if h.Mode != nil {
rets = append(rets, "mode="+h.Mode.String())
}
return base.HeaderValue{strings.Join(rets, ";")}
}