2020-07-11 08:22:54 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net"
|
2020-08-05 10:17:04 +00:00
|
|
|
"regexp"
|
2020-07-12 15:24:12 +00:00
|
|
|
"strconv"
|
2020-07-12 10:34:35 +00:00
|
|
|
|
2020-07-18 11:48:09 +00:00
|
|
|
"github.com/aler9/gortsplib"
|
2020-07-19 09:51:28 +00:00
|
|
|
"github.com/aler9/sdp/v3"
|
2020-07-11 08:22:54 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func parseIpCidrList(in []string) ([]interface{}, error) {
|
|
|
|
if len(in) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var ret []interface{}
|
|
|
|
for _, t := range in {
|
|
|
|
_, ipnet, err := net.ParseCIDR(t)
|
|
|
|
if err == nil {
|
|
|
|
ret = append(ret, ipnet)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
ip := net.ParseIP(t)
|
|
|
|
if ip != nil {
|
|
|
|
ret = append(ret, ip)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("unable to parse ip/network '%s'", t)
|
|
|
|
}
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
2020-07-11 14:40:19 +00:00
|
|
|
func ipEqualOrInRange(ip net.IP, ips []interface{}) bool {
|
|
|
|
for _, item := range ips {
|
|
|
|
switch titem := item.(type) {
|
|
|
|
case net.IP:
|
|
|
|
if titem.Equal(ip) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
case *net.IPNet:
|
|
|
|
if titem.Contains(ip) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-08-25 17:30:37 +00:00
|
|
|
type multiBuffer struct {
|
|
|
|
buffers [][]byte
|
|
|
|
curBuf int
|
2020-07-11 08:48:18 +00:00
|
|
|
}
|
|
|
|
|
2020-08-25 17:30:37 +00:00
|
|
|
func newMultiBuffer(count int, size int) *multiBuffer {
|
|
|
|
buffers := make([][]byte, count)
|
|
|
|
for i := 0; i < count; i++ {
|
|
|
|
buffers[i] = make([]byte, size)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &multiBuffer{
|
|
|
|
buffers: buffers,
|
2020-07-11 08:48:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-25 17:30:37 +00:00
|
|
|
func (mb *multiBuffer) next() []byte {
|
|
|
|
ret := mb.buffers[mb.curBuf]
|
|
|
|
mb.curBuf += 1
|
|
|
|
if mb.curBuf >= len(mb.buffers) {
|
|
|
|
mb.curBuf = 0
|
2020-07-11 08:48:18 +00:00
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
2020-07-12 10:34:35 +00:00
|
|
|
|
2020-07-30 11:31:18 +00:00
|
|
|
// generate a sdp from scratch
|
2020-07-18 11:48:09 +00:00
|
|
|
func sdpForServer(tracks []*gortsplib.Track) (*sdp.SessionDescription, []byte) {
|
2020-07-12 15:24:12 +00:00
|
|
|
sout := &sdp.SessionDescription{
|
2020-07-19 09:51:28 +00:00
|
|
|
SessionName: func() *sdp.SessionName {
|
|
|
|
ret := sdp.SessionName("Stream")
|
|
|
|
return &ret
|
|
|
|
}(),
|
|
|
|
Origin: &sdp.Origin{
|
2020-07-12 15:24:12 +00:00
|
|
|
Username: "-",
|
|
|
|
NetworkType: "IN",
|
|
|
|
AddressType: "IP4",
|
|
|
|
UnicastAddress: "127.0.0.1",
|
|
|
|
},
|
|
|
|
TimeDescriptions: []sdp.TimeDescription{
|
|
|
|
{Timing: sdp.Timing{0, 0}},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2020-07-18 11:48:09 +00:00
|
|
|
for i, track := range tracks {
|
2020-07-12 15:24:12 +00:00
|
|
|
mout := &sdp.MediaDescription{
|
|
|
|
MediaName: sdp.MediaName{
|
2020-07-18 11:48:09 +00:00
|
|
|
Media: track.Media.MediaName.Media,
|
2020-07-12 15:24:12 +00:00
|
|
|
Protos: []string{"RTP", "AVP"}, // override protocol
|
2020-07-18 11:48:09 +00:00
|
|
|
Formats: track.Media.MediaName.Formats,
|
2020-07-12 15:24:12 +00:00
|
|
|
},
|
2020-07-18 11:48:09 +00:00
|
|
|
Bandwidth: track.Media.Bandwidth,
|
2020-07-12 15:24:12 +00:00
|
|
|
Attributes: func() []sdp.Attribute {
|
|
|
|
var ret []sdp.Attribute
|
|
|
|
|
2020-07-18 11:48:09 +00:00
|
|
|
for _, attr := range track.Media.Attributes {
|
2020-07-12 15:24:12 +00:00
|
|
|
if attr.Key == "rtpmap" || attr.Key == "fmtp" {
|
|
|
|
ret = append(ret, attr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-03 15:35:34 +00:00
|
|
|
// control attribute is the path that is appended
|
2020-07-12 15:24:12 +00:00
|
|
|
// to the stream path in SETUP
|
|
|
|
ret = append(ret, sdp.Attribute{
|
|
|
|
Key: "control",
|
|
|
|
Value: "trackID=" + strconv.FormatInt(int64(i), 10),
|
|
|
|
})
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}(),
|
|
|
|
}
|
|
|
|
sout.MediaDescriptions = append(sout.MediaDescriptions, mout)
|
|
|
|
}
|
|
|
|
|
2020-07-19 09:51:28 +00:00
|
|
|
bytsout, _ := sout.Marshal()
|
2020-07-12 15:24:12 +00:00
|
|
|
return sout, bytsout
|
|
|
|
}
|
2020-08-03 14:56:45 +00:00
|
|
|
|
|
|
|
func splitPath(path string) (string, string, error) {
|
2020-08-29 18:49:26 +00:00
|
|
|
pos := func() int {
|
|
|
|
for i := len(path) - 1; i >= 0; i-- {
|
|
|
|
if path[i] == '/' {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}()
|
|
|
|
|
|
|
|
if pos < 0 {
|
2020-08-03 14:56:45 +00:00
|
|
|
return "", "", fmt.Errorf("the path must contain a base path and a control path (%s)", path)
|
|
|
|
}
|
|
|
|
|
2020-08-29 18:49:26 +00:00
|
|
|
basePath := path[:pos]
|
|
|
|
controlPath := path[pos+1:]
|
2020-08-03 14:56:45 +00:00
|
|
|
|
2020-08-29 18:49:26 +00:00
|
|
|
if len(basePath) == 0 {
|
|
|
|
return "", "", fmt.Errorf("empty base path (%s)", basePath)
|
2020-08-03 14:56:45 +00:00
|
|
|
}
|
|
|
|
|
2020-08-29 18:49:26 +00:00
|
|
|
if len(controlPath) == 0 {
|
|
|
|
return "", "", fmt.Errorf("empty control path (%s)", controlPath)
|
2020-08-03 14:56:45 +00:00
|
|
|
}
|
|
|
|
|
2020-08-29 18:49:26 +00:00
|
|
|
return basePath, controlPath, nil
|
2020-08-03 14:56:45 +00:00
|
|
|
}
|
2020-08-03 15:54:23 +00:00
|
|
|
|
2020-08-29 18:49:26 +00:00
|
|
|
var rePathName = regexp.MustCompile("^[0-9a-zA-Z_\\-/]+$")
|
2020-08-05 10:17:04 +00:00
|
|
|
|
|
|
|
func checkPathName(name string) error {
|
|
|
|
if !rePathName.MatchString(name) {
|
2020-08-29 18:49:26 +00:00
|
|
|
return fmt.Errorf("can contain only alfanumeric characters, underscore, minus or slash")
|
|
|
|
}
|
|
|
|
|
|
|
|
if name[0] == '/' {
|
|
|
|
return fmt.Errorf("can't begin with a slash")
|
|
|
|
}
|
|
|
|
|
|
|
|
if name[len(name)-1] == '/' {
|
|
|
|
return fmt.Errorf("can't end with a slash")
|
2020-08-05 10:17:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|