protocol/rtsp: completed test for rtsp

Completed test to check that each method we support works and can produce valid requests. The requests are compared to requests that have been proven valid. This test also checks that the RTSP
client communicates over tcp correctly. i.e. we have set up a client and server, and we check that this communication happens correctly.
This commit is contained in:
Saxon 2019-04-25 14:46:21 +09:30
parent adbb9d1c99
commit f3f3be5fc6
2 changed files with 212 additions and 14 deletions

View File

@ -57,6 +57,7 @@ package rtsp
import ( import (
"bufio" "bufio"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -78,6 +79,10 @@ const (
setup = "SETUP" setup = "SETUP"
) )
const minResponse = 15
var ErrSmallResponse = errors.New("response too small")
// Session describes an RTSP Session. // Session describes an RTSP Session.
type Session struct { type Session struct {
cSeq int cSeq int
@ -242,25 +247,24 @@ func (r Response) String() string {
func ReadResponse(r io.Reader) (res *Response, err error) { func ReadResponse(r io.Reader) (res *Response, err error) {
res = new(Response) res = new(Response)
res.Header = make(map[string][]string) res.Header = make(map[string][]string)
b := bufio.NewReader(r) b := bufio.NewReader(r)
var s string var s string
// TODO: allow CR, LF, or CRLF // TODO: allow CR, LF, or CRLF
if s, err = b.ReadString('\n'); err != nil { if s, err = b.ReadString('\n'); err != nil {
return return
} }
if len(s) < minResponse {
return nil, ErrSmallResponse
}
parts := strings.SplitN(s, " ", 3) parts := strings.SplitN(s, " ", 3)
res.Proto, res.ProtoMajor, res.ProtoMinor, err = parseVersion(parts[0]) res.Proto, res.ProtoMajor, res.ProtoMinor, err = parseVersion(parts[0])
if err != nil { if err != nil {
return return
} }
if res.StatusCode, err = strconv.Atoi(parts[1]); err != nil { if res.StatusCode, err = strconv.Atoi(parts[1]); err != nil {
return return
} }
res.Status = strings.TrimSpace(parts[2]) res.Status = strings.TrimSpace(parts[2])
// read headers // read headers

View File

@ -28,21 +28,215 @@ LICENSE
package rtsp package rtsp
import ( import (
"errors"
"fmt"
"io"
"net"
"net/url"
"strings"
"testing" "testing"
"time"
"unicode"
) )
func TestDescribe(t *testing.T) { func TestMethods(t *testing.T) {
const dummyAddr = "rtsp://admin:admin@192.168.0.50:8554/CH001.sdp"
dummyURL, err := url.Parse(dummyAddr)
if err != nil {
t.Fatalf("could not parse dummy address, failed with err: %v", err)
}
tests := []struct {
method func(s *Session) (*Response, error)
serverRes []byte
expected []byte
}{
{
method: func(s *Session) (*Response, error) {
return s.writeRequest(dummyURL, 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,
},
},
{
method: func(s *Session) (*Response, error) {
return s.writeRequest(dummyURL, options, nil, nil)
},
expected: []byte{
0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x53, 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, 0x31,
0x0d, 0x0a, 0x0d, 0x0a,
},
},
{
method: func(s *Session) (*Response, error) {
url, err := url.Parse(dummyAddr + "/track1")
if err != nil {
t.Fatalf("could not parse url with track, failed with err: %v", err)
}
return s.writeRequest(
url,
setup,
func(req *Request) {
req.Header.Add("Transport", fmt.Sprintf("RTP/AVP;unicast;client_port=%d-%d", 6870, 6871))
},
nil,
)
},
expected: []byte{
0x53, 0x45, 0x54, 0x55, 0x50, 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, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x31, 0x20,
0x52, 0x54, 0x53, 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x0d, 0x0a, 0x54, 0x72,
0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x3a, 0x20, 0x52, 0x54, 0x50,
0x2f, 0x41, 0x56, 0x50, 0x3b, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x73, 0x74,
0x3b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74,
0x3d, 0x36, 0x38, 0x37, 0x30, 0x2d, 0x36, 0x38, 0x37, 0x31, 0x0d, 0x0a,
0x43, 0x53, 0x65, 0x71, 0x3a, 0x20, 0x33, 0x0d, 0x0a, 0x0d, 0x0a,
},
},
{
method: func(s *Session) (*Response, error) {
return s.writeRequest(dummyURL, play, func(req *Request) { req.Header.Add("Session", "00000021") }, nil)
},
expected: []byte{
0x50, 0x4c, 0x41, 0x59, 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, 0x34, 0x0d, 0x0a, 0x53,
0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x32, 0x31, 0x0d, 0x0a, 0x0d, 0x0a,
},
},
}
const serverAddr = "rtsp://localhost:8005"
const retries = 10
clientErr := make(chan error)
serverErr := make(chan error)
done := make(chan struct{})
// start server
go func() {
l, err := net.Listen("tcp", strings.TrimLeft(serverAddr, "rtsp://"))
if err != nil {
serverErr <- errors.New(fmt.Sprintf("server could not listen, error: %v", err))
}
conn, err := l.Accept()
if err != nil {
serverErr <- errors.New(fmt.Sprintf("server could not accept connection, error: %v", err))
}
buf := make([]byte, 1024)
var n int
for i, test := range tests {
loop:
for {
n, err = conn.Read(buf)
err, ok := err.(net.Error)
switch {
case err == nil:
break loop
case err == io.EOF:
case ok && err.Timeout():
default:
serverErr <- errors.New(fmt.Sprintf("server could not read conn, error: %v", err))
return
}
}
conn.Write([]byte{'\n'})
want := test.expected
got := buf[:n]
if !equal(got, want) {
serverErr <- errors.New(fmt.Sprintf("unexpected result for test: %v. \nGot: %v\n Want: %v\n", i, got, want))
}
}
close(done)
}()
// start client
go func() {
var sess *Session
var err error
for retry := 0; ; retry++ {
sess, err = NewSession(serverAddr)
if err == nil {
break
}
if retry > 10 {
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 {
clientErr <- errors.New(fmt.Sprintf("error request for: %v err: %v", i, err))
}
}
}()
// start error checking
for {
select {
case err := <-clientErr:
t.Fatalf("client error: %v", err)
case err := <-serverErr:
t.Fatalf("server error: %v", err)
case <-done:
return
default:
}
}
} }
func TestOptions(t *testing.T) { func equal(got, want []byte) bool {
const eol = "\r\n"
gotParts := strings.Split(strings.TrimRight(string(got), eol), eol)
wantParts := strings.Split(strings.TrimRight(string(want), eol), eol)
gotParts, ok := rmSeqNum(gotParts)
if !ok {
return false
}
wantParts, ok = rmSeqNum(wantParts)
if !ok {
return false
}
for _, gotStr := range gotParts {
for i, wantStr := range wantParts {
if gotStr == wantStr {
wantParts = append(wantParts[:i], wantParts[i+1:]...)
}
}
}
return len(wantParts) == 0
} }
func TestSetup(t *testing.T) { func rmSeqNum(s []string) ([]string, bool) {
for i, _s := range s {
} if strings.Contains(_s, "CSeq") {
s[i] = strings.TrimFunc(s[i], func(r rune) bool { return unicode.IsNumber(r) })
func TestPlay(t *testing.T) { return s, true
}
}
return nil, false
} }