Browse Source

return 400 in case of errors

pull/2/head
aler9 6 years ago
parent
commit
8a773a1b16
  1. 375
      rtsp_client.go

375
rtsp_client.go

@ -3,6 +3,7 @@ package main
import ( import (
"bufio" "bufio"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -14,6 +15,12 @@ import (
"rtsp-server/rtsp" "rtsp-server/rtsp"
) )
var (
errTeardown = errors.New("teardown")
errPlay = errors.New("play")
errRecord = errors.New("record")
)
type rtspClient struct { type rtspClient struct {
p *program p *program
nconn net.Conn nconn net.Conn
@ -92,16 +99,133 @@ func (c *rtspClient) run() {
c.log(req.Method) c.log(req.Method)
res, err := c.handleRequest(req)
switch err {
// normal response
case nil:
err = rconn.WriteResponse(res)
if err != nil {
c.log("ERR: %s", err)
return
}
// TEARDOWN, close connection silently
case errTeardown:
return
// PLAY: first write response, then set state
// otherwise, in case of TCP connections, RTP packets could be written
// before the response
// then switch to RTP if TCP
case errPlay:
err = rconn.WriteResponse(res)
if err != nil {
c.log("ERR: %s", err)
return
}
c.log("is receiving (via %s)", c.rtpProto)
c.p.mutex.Lock()
c.state = "PLAY"
c.p.mutex.Unlock()
// when rtp protocol is TCP, the RTSP connection becomes a RTP connection
// receive RTP feedback, do not parse it, wait until connection closes
if c.rtpProto == "tcp" {
buf := make([]byte, 1024)
for {
_, err := c.nconn.Read(buf)
if err != nil {
return
}
}
}
// RECORD: switch to RTP if TCP
case errRecord:
err = rconn.WriteResponse(res)
if err != nil {
c.log("ERR: %s", err)
return
}
c.p.mutex.Lock()
c.state = "RECORD"
c.p.mutex.Unlock()
c.log("is publishing (via %s)", c.rtpProto)
// when rtp protocol is TCP, the RTSP connection becomes a RTP connection
// receive RTP data and parse it
if c.rtpProto == "tcp" {
packet := make([]byte, 2048)
bconn := bufio.NewReader(c.nconn)
for {
byts, err := bconn.Peek(4)
if err != nil {
return
}
bconn.Discard(4)
if byts[0] != 0x24 {
c.log("ERR: wrong magic byte")
return
}
if byts[1] != 0x00 {
c.log("ERR: wrong channel")
return
}
plen := binary.BigEndian.Uint16(byts[2:])
if plen > 2048 {
c.log("ERR: packet len > 2048")
return
}
_, err = io.ReadFull(bconn, packet[:plen])
if err != nil {
return
}
c.p.handleRtp(packet[:plen])
}
}
// error: write and exit
default:
c.log("ERR: %s", err)
if cseq, ok := req.Headers["cseq"]; ok {
rconn.WriteResponse(&rtsp.Response{
StatusCode: 400,
Status: "Bad Request",
Headers: map[string]string{
"CSeq": cseq,
},
})
} else {
rconn.WriteResponse(&rtsp.Response{
StatusCode: 400,
Status: "Bad Request",
})
}
return
}
}
}
func (c *rtspClient) handleRequest(req *rtsp.Request) (*rtsp.Response, error) {
cseq, ok := req.Headers["CSeq"] cseq, ok := req.Headers["CSeq"]
if !ok { if !ok {
c.log("ERR: cseq missing") return nil, fmt.Errorf("cseq missing")
return
} }
ur, err := url.Parse(req.Path) ur, err := url.Parse(req.Path)
if err != nil { if err != nil {
c.log("ERR: unable to parse path '%s'", req.Path) return nil, fmt.Errorf("unable to parse path '%s'", req.Path)
return
} }
switch req.Method { switch req.Method {
@ -109,7 +233,7 @@ func (c *rtspClient) run() {
// do not check state, since OPTIONS can be requested // do not check state, since OPTIONS can be requested
// in any state // in any state
err = rconn.WriteResponse(&rtsp.Response{ return &rtsp.Response{
StatusCode: 200, StatusCode: 200,
Status: "OK", Status: "OK",
Headers: map[string]string{ Headers: map[string]string{
@ -124,16 +248,11 @@ func (c *rtspClient) run() {
"TEARDOWN", "TEARDOWN",
}, ", "), }, ", "),
}, },
}) }, nil
if err != nil {
c.log("ERR: %s", err)
return
}
case "DESCRIBE": case "DESCRIBE":
if c.state != "STARTING" { if c.state != "STARTING" {
c.log("ERR: client is in state '%s'", c.state) return nil, fmt.Errorf("client is in state '%s'", c.state)
return
} }
sdp, err := func() ([]byte, error) { sdp, err := func() ([]byte, error) {
@ -147,11 +266,10 @@ func (c *rtspClient) run() {
return c.p.streamSdp, nil return c.p.streamSdp, nil
}() }()
if err != nil { if err != nil {
c.log("ERR: %s", err) return nil, err
return
} }
err = rconn.WriteResponse(&rtsp.Response{ return &rtsp.Response{
StatusCode: 200, StatusCode: 200,
Status: "OK", Status: "OK",
Headers: map[string]string{ Headers: map[string]string{
@ -160,27 +278,20 @@ func (c *rtspClient) run() {
"Content-Type": "application/sdp", "Content-Type": "application/sdp",
}, },
Content: sdp, Content: sdp,
}) }, nil
if err != nil {
c.log("ERR: %s", err)
return
}
case "ANNOUNCE": case "ANNOUNCE":
if c.state != "STARTING" { if c.state != "STARTING" {
c.log("ERR: client is in state '%s'", c.state) return nil, fmt.Errorf("client is in state '%s'", c.state)
return
} }
ct, ok := req.Headers["Content-Type"] ct, ok := req.Headers["Content-Type"]
if !ok { if !ok {
c.log("ERR: Content-Type header missing") return nil, fmt.Errorf("Content-Type header missing")
return
} }
if ct != "application/sdp" { if ct != "application/sdp" {
c.log("ERR: unsupported Content-Type '%s'", ct) return nil, fmt.Errorf("unsupported Content-Type '%s'", ct)
return
} }
err := func() error { err := func() error {
@ -196,31 +307,25 @@ func (c *rtspClient) run() {
return nil return nil
}() }()
if err != nil { if err != nil {
c.log("ERR: %s", err) return nil, err
return
} }
err = rconn.WriteResponse(&rtsp.Response{ c.p.mutex.Lock()
c.state = "ANNOUNCE"
c.p.mutex.Unlock()
return &rtsp.Response{
StatusCode: 200, StatusCode: 200,
Status: "OK", Status: "OK",
Headers: map[string]string{ Headers: map[string]string{
"CSeq": cseq, "CSeq": cseq,
}, },
}) }, nil
if err != nil {
c.log("ERR: %s", err)
return
}
c.p.mutex.Lock()
c.state = "ANNOUNCE"
c.p.mutex.Unlock()
case "SETUP": case "SETUP":
transport, ok := req.Headers["Transport"] transport, ok := req.Headers["Transport"]
if !ok { if !ok {
c.log("ERR: transport header missing") return nil, fmt.Errorf("transport header missing")
return
} }
transports := make(map[string]struct{}) transports := make(map[string]struct{})
@ -229,8 +334,7 @@ func (c *rtspClient) run() {
} }
if _, ok := transports["unicast"]; !ok { if _, ok := transports["unicast"]; !ok {
c.log("ERR: transport header does not contain unicast") return nil, fmt.Errorf("transport header does not contain unicast")
return
} }
getPorts := func() (int, int) { getPorts := func() (int, int) {
@ -267,11 +371,17 @@ func (c *rtspClient) run() {
if _, ok := transports["RTP/AVP"]; ok { if _, ok := transports["RTP/AVP"]; ok {
clientPort1, clientPort2 := getPorts() clientPort1, clientPort2 := getPorts()
if clientPort1 == 0 || clientPort2 == 0 { if clientPort1 == 0 || clientPort2 == 0 {
c.log("ERR: transport header does not have valid client ports (%s)", transport) return nil, fmt.Errorf("transport header does not have valid client ports (%s)", transport)
return
} }
err = rconn.WriteResponse(&rtsp.Response{ c.p.mutex.Lock()
c.rtpProto = "udp"
c.rtpPort = clientPort1
c.rtcpPort = clientPort2
c.state = "PRE_PLAY"
c.p.mutex.Unlock()
return &rtsp.Response{
StatusCode: 200, StatusCode: 200,
Status: "OK", Status: "OK",
Headers: map[string]string{ Headers: map[string]string{
@ -287,22 +397,16 @@ func (c *rtspClient) run() {
}, ";"), }, ";"),
"Session": "12345678", "Session": "12345678",
}, },
}) }, nil
if err != nil {
c.log("ERR: %s", err)
return
}
// TCP
} else if _, ok := transports["RTP/AVP/TCP"]; ok {
c.p.mutex.Lock() c.p.mutex.Lock()
c.rtpProto = "udp" c.rtpProto = "tcp"
c.rtpPort = clientPort1
c.rtcpPort = clientPort2
c.state = "PRE_PLAY" c.state = "PRE_PLAY"
c.p.mutex.Unlock() c.p.mutex.Unlock()
// TCP return &rtsp.Response{
} else if _, ok := transports["RTP/AVP/TCP"]; ok {
err = rconn.WriteResponse(&rtsp.Response{
StatusCode: 200, StatusCode: 200,
Status: "OK", Status: "OK",
Headers: map[string]string{ Headers: map[string]string{
@ -316,37 +420,32 @@ func (c *rtspClient) run() {
}, ";"), }, ";"),
"Session": "12345678", "Session": "12345678",
}, },
}) }, nil
if err != nil {
c.log("ERR: %s", err)
return
}
c.p.mutex.Lock()
c.rtpProto = "tcp"
c.state = "PRE_PLAY"
c.p.mutex.Unlock()
} else { } else {
c.log("ERR: transport header does not contain a valid protocol (RTP/AVP or RTP/AVP/TCP) (%s)", transport) return nil, fmt.Errorf("transport header does not contain a valid protocol (RTP/AVP or RTP/AVP/TCP) (%s)", transport)
return
} }
// record // record
case "ANNOUNCE": case "ANNOUNCE":
if _, ok := transports["mode=record"]; !ok { if _, ok := transports["mode=record"]; !ok {
c.log("ERR: transport header does not contain mode=record") return nil, fmt.Errorf("transport header does not contain mode=record")
return
} }
if _, ok := transports["RTP/AVP/UDP"]; ok { if _, ok := transports["RTP/AVP/UDP"]; ok {
clientPort1, clientPort2 := getPorts() clientPort1, clientPort2 := getPorts()
if clientPort1 == 0 || clientPort2 == 0 { if clientPort1 == 0 || clientPort2 == 0 {
c.log("ERR: transport header does not have valid client ports (%s)", transport) return nil, fmt.Errorf("transport header does not have valid client ports (%s)", transport)
return
} }
err = rconn.WriteResponse(&rtsp.Response{ c.p.mutex.Lock()
c.rtpProto = "udp"
c.rtpPort = clientPort1
c.rtcpPort = clientPort2
c.state = "PRE_RECORD"
c.p.mutex.Unlock()
return &rtsp.Response{
StatusCode: 200, StatusCode: 200,
Status: "OK", Status: "OK",
Headers: map[string]string{ Headers: map[string]string{
@ -360,21 +459,15 @@ func (c *rtspClient) run() {
}, ";"), }, ";"),
"Session": "12345678", "Session": "12345678",
}, },
}) }, nil
if err != nil {
c.log("ERR: %s", err)
return
}
} else if _, ok := transports["RTP/AVP/TCP"]; ok {
c.p.mutex.Lock() c.p.mutex.Lock()
c.rtpProto = "udp" c.rtpProto = "tcp"
c.rtpPort = clientPort1
c.rtcpPort = clientPort2
c.state = "PRE_RECORD" c.state = "PRE_RECORD"
c.p.mutex.Unlock() c.p.mutex.Unlock()
} else if _, ok := transports["RTP/AVP/TCP"]; ok { return &rtsp.Response{
err = rconn.WriteResponse(&rtsp.Response{
StatusCode: 200, StatusCode: 200,
Status: "OK", Status: "OK",
Headers: map[string]string{ Headers: map[string]string{
@ -387,68 +480,33 @@ func (c *rtspClient) run() {
}, ";"), }, ";"),
"Session": "12345678", "Session": "12345678",
}, },
}) }, nil
if err != nil {
c.log("ERR: %s", err)
return
}
c.p.mutex.Lock()
c.rtpProto = "tcp"
c.state = "PRE_RECORD"
c.p.mutex.Unlock()
} else { } else {
c.log("ERR: transport header does not contain a valid protocol (RTP/AVP or RTP/AVP/TCP) (%s)", transport) return nil, fmt.Errorf("transport header does not contain a valid protocol (RTP/AVP or RTP/AVP/TCP) (%s)", transport)
return
} }
default: default:
c.log("ERR: client is in state '%s'", c.state) return nil, fmt.Errorf("client is in state '%s'", c.state)
return
} }
case "PLAY": case "PLAY":
if c.state != "PRE_PLAY" { if c.state != "PRE_PLAY" {
c.log("ERR: client is in state '%s'", c.state) return nil, fmt.Errorf("client is in state '%s'", c.state)
return
} }
err = rconn.WriteResponse(&rtsp.Response{ return &rtsp.Response{
StatusCode: 200, StatusCode: 200,
Status: "OK", Status: "OK",
Headers: map[string]string{ Headers: map[string]string{
"CSeq": cseq, "CSeq": cseq,
"Session": "12345678", "Session": "12345678",
}, },
}) }, errPlay
if err != nil {
c.log("ERR: %s", err)
return
}
c.log("is receiving (via %s)", c.rtpProto)
c.p.mutex.Lock()
c.state = "PLAY"
c.p.mutex.Unlock()
// when rtp protocol is TCP, the RTSP connection becomes a RTP connection.
// receive RTP feedback, do not parse it, wait until connection closes.
if c.rtpProto == "tcp" {
buf := make([]byte, 1024)
for {
_, err := c.nconn.Read(buf)
if err != nil {
return
}
}
}
case "PAUSE": case "PAUSE":
if c.state != "PLAY" { if c.state != "PLAY" {
c.log("ERR: client is in state '%s'", c.state) return nil, fmt.Errorf("client is in state '%s'", c.state)
return
} }
c.log("paused receiving") c.log("paused receiving")
@ -457,86 +515,33 @@ func (c *rtspClient) run() {
c.state = "PRE_PLAY" c.state = "PRE_PLAY"
c.p.mutex.Unlock() c.p.mutex.Unlock()
err = rconn.WriteResponse(&rtsp.Response{ return &rtsp.Response{
StatusCode: 200, StatusCode: 200,
Status: "OK", Status: "OK",
Headers: map[string]string{ Headers: map[string]string{
"CSeq": cseq, "CSeq": cseq,
"Session": "12345678", "Session": "12345678",
}, },
}) }, nil
if err != nil {
c.log("ERR: %s", err)
return
}
case "RECORD": case "RECORD":
if c.state != "PRE_RECORD" { if c.state != "PRE_RECORD" {
c.log("ERR: client is in state '%s'", c.state) return nil, fmt.Errorf("client is in state '%s'", c.state)
return
} }
err = rconn.WriteResponse(&rtsp.Response{ return &rtsp.Response{
StatusCode: 200, StatusCode: 200,
Status: "OK", Status: "OK",
Headers: map[string]string{ Headers: map[string]string{
"CSeq": cseq, "CSeq": cseq,
"Session": "12345678", "Session": "12345678",
}, },
}) }, errRecord
if err != nil {
c.log("ERR: %s", err)
return
}
c.log("is publishing (via %s)", c.rtpProto)
c.p.mutex.Lock()
c.state = "RECORD"
c.p.mutex.Unlock()
// when rtp protocol is TCP, the RTSP connection becomes a RTP connection.
// receive RTP feedback, do not parse it, wait until connection closes.
if c.rtpProto == "tcp" {
packet := make([]byte, 2048)
bconn := bufio.NewReader(c.nconn)
for {
byts, err := bconn.Peek(4)
if err != nil {
return
}
bconn.Discard(4)
if byts[0] != 0x24 {
c.log("ERR: wrong magic byte")
return
}
if byts[1] != 0x00 {
c.log("ERR: wrong channel")
return
}
plen := binary.BigEndian.Uint16(byts[2:])
if plen > 2048 {
c.log("ERR: packet len > 2048")
return
}
_, err = io.ReadFull(bconn, packet[:plen])
if err != nil {
return
}
c.p.handleRtp(packet[:plen])
}
}
case "TEARDOWN": case "TEARDOWN":
return return nil, errTeardown
default: default:
c.log("ERR: method %s unhandled", req.Method) return nil, fmt.Errorf("unhandled method '%s'", req.Method)
}
} }
} }

Loading…
Cancel
Save