protocol/rtsp: general clean up

This commit is contained in:
Saxon 2019-04-25 15:30:28 +09:30
parent f3f3be5fc6
commit 9169afe34f
3 changed files with 54 additions and 38 deletions

View File

@ -64,9 +64,9 @@ import (
)
func main() {
rtspServerPtr := flag.String("rtsp-server", "", "The RTSP server we would like to get video from")
clientPortPtr := flag.Uint("port", 6870, "The port on the client we would like to receive RTP on")
trackPtr := flag.String("track", "track1", "The track that we would like to receive media from")
rtspServerPtr := flag.String("rtsp-server", "", "The RTSP server we would like to get video from.")
clientPortPtr := flag.Uint("port", 6870, "The port on the client we would like to receive RTP on.")
trackPtr := flag.String("track", "track1", "The track that we would like to receive media from.")
flag.Parse()
sess, err := rtsp.NewSession(*rtspServerPtr)

View File

@ -4,7 +4,8 @@ NAME
DESCRIPTION
rtsp.go provides functionality for forming and sending RTSP requests for
methods, DESCRIBE, OPTIONS, SETUP and PLAY.
methods, DESCRIBE, OPTIONS, SETUP and PLAY, as described by
the RTSP standards, see https://tools.ietf.org/html/rfc7826
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
@ -68,9 +69,6 @@ import (
"strings"
)
// rtsp protocol described by:
// https://tools.ietf.org/html/rfc7826
// RTSP methods.
const (
describe = "DESCRIBE"
@ -79,9 +77,10 @@ const (
setup = "SETUP"
)
// Minimum response size to be considered valid.
const minResponse = 15
var ErrSmallResponse = errors.New("response too small")
var errSmallResponse = errors.New("response too small")
// Session describes an RTSP Session.
type Session struct {
@ -144,7 +143,7 @@ func (s *Session) Play() (*Response, error) {
// writeRequest creates an RTSP request of the method given and writes it to the
// the Session connection.
//
//headerOp and respOp define any operation that needs
// headerOp and respOp define any operation that needs
// to occur to the request or response for the given method.
func (s *Session) writeRequest(url *url.URL, method string, headerOp func(*Request), respOp func(*Response)) (*Response, error) {
req, err := NewRequest(method, s.nextCSeq(), url, nil)
@ -251,11 +250,10 @@ func ReadResponse(r io.Reader) (res *Response, err error) {
var s string
// TODO: allow CR, LF, or CRLF
if s, err = b.ReadString('\n'); err != nil {
return
}
if len(s) < minResponse {
return nil, ErrSmallResponse
return nil, errSmallResponse
}
parts := strings.SplitN(s, " ", 3)
res.Proto, res.ProtoMajor, res.ProtoMinor, err = parseVersion(parts[0])

View File

@ -1,10 +1,9 @@
/*
NAME
rtsp.go
rtsp_test.go
DESCRIPTION
rtsp.go provides functionality for forming and sending RTSP requests for
methods, DESCRIBE, OPTIONS, SETUP and PLAY.
rtsp_test.go provides a test to check functionality provided in rtsp.go.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
@ -39,38 +38,43 @@ import (
"unicode"
)
// TestMethods checks that we can correctly form requests for each of the RTSP
// methods supported in the rtsp pkg. This test also checks that communication
// over a TCP connection is performed correctly.
func TestMethods(t *testing.T) {
const dummyAddr = "rtsp://admin:admin@192.168.0.50:8554/CH001.sdp"
dummyURL, err := url.Parse(dummyAddr)
const dummyURL = "rtsp://admin:admin@192.168.0.50:8554/CH001.sdp"
url, err := url.Parse(dummyURL)
if err != nil {
t.Fatalf("could not parse dummy address, failed with err: %v", err)
}
// tests holds tests which consist of a function used to create and write a
// request of a particular method, and also the expected request bytes
// to be received on the server side. The bytes in these tests have been
// obtained from a valid RTSP session.
tests := []struct {
method func(s *Session) (*Response, error)
serverRes []byte
expected []byte
method func(s *Session) (*Response, error)
expected []byte
}{
{
method: func(s *Session) (*Response, error) {
return s.writeRequest(dummyURL, describe, func(req *Request) { req.Header.Add("Accept", "application/sdp") }, nil)
return s.writeRequest(url, describe, func(req *Request) { req.Header.Add("Accept", "application/sdp") }, nil)
},
expected: []byte{
0x44, 0x45, 0x53, 0x43, 0x52, 0x49, 0x42, 0x45, 0x20, 0x72, 0x74,
0x73, 0x70, 0x3a, 0x2f, 0x2f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x3a,
0x61, 0x64, 0x6d, 0x69, 0x6e, 0x40, 0x31, 0x39, 0x32, 0x2e, 0x31,
0x36, 0x38, 0x2e, 0x30, 0x2e, 0x35, 0x30, 0x3a, 0x38, 0x35, 0x35,
0x34, 0x2f, 0x43, 0x48, 0x30, 0x30, 0x31, 0x2e, 0x73, 0x64, 0x70,
0x20, 0x52, 0x54, 0x53, 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x0d, 0x0a,
0x43, 0x53, 0x65, 0x71, 0x3a, 0x20, 0x32, 0x0d, 0x0a, 0x41, 0x63,
0x63, 0x65, 0x70, 0x74, 0x3a, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69,
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x64, 0x70, 0x0d,
0x0a, 0x0d, 0x0a,
0x44, 0x45, 0x53, 0x43, 0x52, 0x49, 0x42, 0x45, 0x20, 0x72, 0x74, 0x73,
0x70, 0x3a, 0x2f, 0x2f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x3a, 0x61, 0x64,
0x6d, 0x69, 0x6e, 0x40, 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e,
0x30, 0x2e, 0x35, 0x30, 0x3a, 0x38, 0x35, 0x35, 0x34, 0x2f, 0x43, 0x48,
0x30, 0x30, 0x31, 0x2e, 0x73, 0x64, 0x70, 0x20, 0x52, 0x54, 0x53, 0x50,
0x2f, 0x31, 0x2e, 0x30, 0x0d, 0x0a, 0x43, 0x53, 0x65, 0x71, 0x3a, 0x20,
0x32, 0x0d, 0x0a, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x3a, 0x20, 0x61,
0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73,
0x64, 0x70, 0x0d, 0x0a, 0x0d, 0x0a,
},
},
{
method: func(s *Session) (*Response, error) {
return s.writeRequest(dummyURL, options, nil, nil)
return s.writeRequest(url, options, nil, nil)
},
expected: []byte{
0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x53, 0x20, 0x72, 0x74, 0x73, 0x70,
@ -84,7 +88,7 @@ func TestMethods(t *testing.T) {
},
{
method: func(s *Session) (*Response, error) {
url, err := url.Parse(dummyAddr + "/track1")
url, err := url.Parse(dummyURL + "/track1")
if err != nil {
t.Fatalf("could not parse url with track, failed with err: %v", err)
}
@ -113,7 +117,7 @@ func TestMethods(t *testing.T) {
},
{
method: func(s *Session) (*Response, error) {
return s.writeRequest(dummyURL, play, func(req *Request) { req.Header.Add("Session", "00000021") }, nil)
return s.writeRequest(url, play, func(req *Request) { req.Header.Add("Session", "00000021") }, nil)
},
expected: []byte{
0x50, 0x4c, 0x41, 0x59, 0x20, 0x72, 0x74, 0x73, 0x70, 0x3a, 0x2f, 0x2f,
@ -130,11 +134,12 @@ func TestMethods(t *testing.T) {
const serverAddr = "rtsp://localhost:8005"
const retries = 10
clientErr := make(chan error)
serverErr := make(chan error)
done := make(chan struct{})
// start server
// This routine acts as the server.
go func() {
l, err := net.Listen("tcp", strings.TrimLeft(serverAddr, "rtsp://"))
if err != nil {
@ -153,6 +158,7 @@ func TestMethods(t *testing.T) {
for {
n, err = conn.Read(buf)
err, ok := err.(net.Error)
switch {
case err == nil:
break loop
@ -163,7 +169,10 @@ func TestMethods(t *testing.T) {
return
}
}
// Write a dummy response, client won't care.
conn.Write([]byte{'\n'})
want := test.expected
got := buf[:n]
if !equal(got, want) {
@ -173,29 +182,33 @@ func TestMethods(t *testing.T) {
close(done)
}()
// start client
// This routine acts as the client.
go func() {
var sess *Session
var err error
// Keep trying to connect to server.
for retry := 0; ; retry++ {
sess, err = NewSession(serverAddr)
if err == nil {
break
}
if retry > 10 {
if retry > retries {
clientErr <- errors.New(fmt.Sprintf("client could not connect to server, error: %v", err))
}
time.Sleep(10 * time.Millisecond)
}
for i, test := range tests {
_, err = test.method(sess)
if err != nil && err != io.EOF && err != ErrSmallResponse {
if err != nil && err != io.EOF && err != errSmallResponse {
clientErr <- errors.New(fmt.Sprintf("error request for: %v err: %v", i, err))
}
}
}()
// start error checking
// We check for errors or a done signal from the server and client routines.
for {
select {
case err := <-clientErr:
@ -209,6 +222,9 @@ func TestMethods(t *testing.T) {
}
}
// equal checks that the got slice is considered equivalent to the want slice,
// neglecting unimportant differences such as order of items in header and the
// CSeq number.
func equal(got, want []byte) bool {
const eol = "\r\n"
gotParts := strings.Split(strings.TrimRight(string(got), eol), eol)
@ -231,6 +247,8 @@ func equal(got, want []byte) bool {
return len(wantParts) == 0
}
// rmSeqNum removes the CSeq number from a string in []string that contains it.
// If a CSeq field is not found nil and false is returned.
func rmSeqNum(s []string) ([]string, bool) {
for i, _s := range s {
if strings.Contains(_s, "CSeq") {