mirror of https://bitbucket.org/ausocean/av.git
protocol/rtcp: finished client_test.go improved usability or client
Finished writing the client_test.go file and through the process fixed some bugs in the client. Also increased usability by providing a Stop() method so that the send and recv routines, and also the connection can be terminated. Also created a sender report struct in rtcp.go - this helped with testing.
This commit is contained in:
parent
aa947d112c
commit
1a19412223
|
@ -8,14 +8,19 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"bitbucket.org/ausocean/utils/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
senderSSRC = 3605043418
|
senderSSRC = 3605043418
|
||||||
defaultClientName = "client"
|
defaultClientName = "client"
|
||||||
delayUnit = 1.0 / 65536.0
|
delayUnit = 1.0 / 65536.0
|
||||||
|
pkg = "rtcp: "
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type log func(lvl int8, msg string, args ...interface{})
|
||||||
|
|
||||||
// client is an rtcp client that will hadle receiving SenderReports from a server
|
// client is an rtcp client that will hadle receiving SenderReports from a server
|
||||||
// and sending out ReceiverReports.
|
// and sending out ReceiverReports.
|
||||||
type client struct {
|
type client struct {
|
||||||
|
@ -30,32 +35,44 @@ type client struct {
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
receiveTime time.Time
|
receiveTime time.Time
|
||||||
buf [200]byte
|
buf [200]byte
|
||||||
|
conn *net.UDPConn
|
||||||
|
wg sync.WaitGroup
|
||||||
|
quitSend chan struct{}
|
||||||
|
quitRecv chan struct{}
|
||||||
|
log
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient returns a pointer to a new client.
|
// NewClient returns a pointer to a new client.
|
||||||
func NewClient(clientAddress, serverAddress, name string, sendInterval time.Duration, rtpSSRC uint32) (*client, error) {
|
func NewClient(clientAddress, serverAddress, name string, sendInterval time.Duration, rtpSSRC uint32, l log) (*client, error) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = defaultClientName
|
name = defaultClientName
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &client{
|
c := &client{
|
||||||
name: name,
|
name: name,
|
||||||
ErrChan: make(chan error),
|
ErrChan: make(chan error, 2),
|
||||||
|
quitSend: make(chan struct{}),
|
||||||
|
quitRecv: make(chan struct{}),
|
||||||
interval: sendInterval,
|
interval: sendInterval,
|
||||||
sourceSSRC: rtpSSRC,
|
sourceSSRC: rtpSSRC,
|
||||||
|
log: l,
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
c.cAddr, err = net.ResolveUDPAddr("udp", clientAddress)
|
c.cAddr, err = net.ResolveUDPAddr("udp", clientAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(fmt.Sprintf("can't resolve client address, failed with error: %v", err))
|
return nil, errors.New(fmt.Sprintf("can't resolve client address, failed with error: %v\n", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
c.sAddr, err = net.ResolveUDPAddr("udp", serverAddress)
|
c.sAddr, err = net.ResolveUDPAddr("udp", serverAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(fmt.Sprintf("can't resolve server address, failed with error: %v", err))
|
return nil, errors.New(fmt.Sprintf("can't resolve server address, failed with error: %v\n", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.conn, err = net.DialUDP("udp", c.cAddr, c.sAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New(fmt.Sprintf("can't dial, failed with error: %v\n", err))
|
||||||
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,77 +80,102 @@ func NewClient(clientAddress, serverAddress, name string, sendInterval time.Dura
|
||||||
// receiving and parsing sender reports, and the process of sending receiver
|
// receiving and parsing sender reports, and the process of sending receiver
|
||||||
// reports to the server.
|
// reports to the server.
|
||||||
func (c *client) Start() {
|
func (c *client) Start() {
|
||||||
go c.listen()
|
c.log(logger.Debug, pkg+"client is starting")
|
||||||
|
c.wg.Add(1)
|
||||||
|
go c.recv()
|
||||||
|
c.wg.Add(1)
|
||||||
go c.send()
|
go c.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop sends a quit signal to the send and receive routines and closes the
|
||||||
|
// udp connection. It will wait until both routines have returned.
|
||||||
|
func (c *client) Stop() {
|
||||||
|
c.log(logger.Debug, pkg+"client is stopping")
|
||||||
|
close(c.quitSend)
|
||||||
|
close(c.quitRecv)
|
||||||
|
c.conn.Close()
|
||||||
|
c.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
// listen reads from the UDP connection and parses SenderReports.
|
// listen reads from the UDP connection and parses SenderReports.
|
||||||
func (c *client) listen() {
|
func (c *client) recv() {
|
||||||
conn, err := net.ListenUDP("udp", c.cAddr)
|
defer c.wg.Done()
|
||||||
if err != nil {
|
c.log(logger.Debug, pkg+"client is receiving")
|
||||||
c.ErrChan <- err
|
|
||||||
}
|
|
||||||
buf := make([]byte, 4096)
|
buf := make([]byte, 4096)
|
||||||
for {
|
for {
|
||||||
n, _, _ := conn.ReadFromUDP(buf)
|
select {
|
||||||
c.parse(buf[:n])
|
case <-c.quitRecv:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
n, _, err := c.conn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
c.ErrChan <- err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.log(logger.Debug, pkg+"sender report received", "report", buf[:n])
|
||||||
|
c.parse(buf[:n])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// send writes receiver reports to the server.
|
// send writes receiver reports to the server.
|
||||||
func (c *client) send() {
|
func (c *client) send() {
|
||||||
conn, err := net.DialUDP("udp", c.cAddr, c.sAddr)
|
defer c.wg.Done()
|
||||||
if err != nil {
|
c.log(logger.Debug, pkg+"client is sending")
|
||||||
c.ErrChan <- err
|
|
||||||
}
|
|
||||||
for {
|
for {
|
||||||
time.Sleep(c.interval)
|
select {
|
||||||
|
case <-c.quitSend:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
time.Sleep(c.interval)
|
||||||
|
|
||||||
report := ReceiverReport{
|
report := ReceiverReport{
|
||||||
Header: Header{
|
Header: Header{
|
||||||
Version: 2,
|
Version: 2,
|
||||||
Padding: false,
|
Padding: false,
|
||||||
ReportCount: 1,
|
ReportCount: 1,
|
||||||
Type: typeReceiverReport,
|
Type: typeReceiverReport,
|
||||||
},
|
|
||||||
SenderSSRC: senderSSRC,
|
|
||||||
Blocks: []ReportBlock{
|
|
||||||
ReportBlock{
|
|
||||||
SSRC: c.sourceSSRC,
|
|
||||||
FractionLost: 0,
|
|
||||||
PacketsLost: math.MaxUint32,
|
|
||||||
HighestSequence: c.highestSequence(),
|
|
||||||
Jitter: c.jitter(),
|
|
||||||
LSR: c.lastSenderTs(),
|
|
||||||
DLSR: c.delay(),
|
|
||||||
},
|
},
|
||||||
},
|
SenderSSRC: senderSSRC,
|
||||||
Extensions: nil,
|
Blocks: []ReportBlock{
|
||||||
}
|
ReportBlock{
|
||||||
|
SSRC: c.sourceSSRC,
|
||||||
|
FractionLost: 0,
|
||||||
|
PacketsLost: math.MaxUint32,
|
||||||
|
HighestSequence: c.highestSequence(),
|
||||||
|
Jitter: c.jitter(),
|
||||||
|
LSR: c.lastSenderTs(),
|
||||||
|
DLSR: c.delay(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Extensions: nil,
|
||||||
|
}
|
||||||
|
|
||||||
description := SourceDescription{
|
description := SourceDescription{
|
||||||
Header: Header{
|
Header: Header{
|
||||||
Version: 2,
|
Version: 2,
|
||||||
Padding: false,
|
Padding: false,
|
||||||
ReportCount: 1,
|
ReportCount: 1,
|
||||||
Type: typeSourceDescription,
|
Type: typeSourceDescription,
|
||||||
},
|
},
|
||||||
Chunks: []Chunk{
|
Chunks: []Chunk{
|
||||||
Chunk{
|
Chunk{
|
||||||
SSRC: senderSSRC,
|
SSRC: senderSSRC,
|
||||||
Items: []SDESItem{
|
Items: []SDESItem{
|
||||||
SDESItem{
|
SDESItem{
|
||||||
Type: typeCName,
|
Type: typeCName,
|
||||||
Text: []byte(c.name),
|
Text: []byte(c.name),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
}
|
|
||||||
|
|
||||||
_, err := conn.Write(c.formPayload(&report, &description))
|
c.log(logger.Debug, pkg+"sending receiver report")
|
||||||
if err != nil {
|
_, err := c.conn.Write(c.formPayload(&report, &description))
|
||||||
c.ErrChan <- err
|
if err != nil {
|
||||||
|
c.ErrChan <- err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,18 @@ package rtcp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"bitbucket.org/ausocean/utils/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TestFromPayload checks that formPayload is working as expected.
|
||||||
func TestFormPayload(t *testing.T) {
|
func TestFormPayload(t *testing.T) {
|
||||||
expect := []byte{
|
expect := []byte{
|
||||||
0x81, 0xc9, 0x00, 0x07,
|
0x81, 0xc9, 0x00, 0x07,
|
||||||
|
@ -80,29 +87,116 @@ func TestFormPayload(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// dummyLogger will allow logging to be done by the testing pkg.
|
||||||
func TestReceiveAndSend(t *testing.T) {
|
type dummyLogger testing.T
|
||||||
quit := make(chan struct{})
|
|
||||||
go testServer(quit)
|
func (dl *dummyLogger) log(lvl int8, msg string, args ...interface{}) {
|
||||||
|
var l string
|
||||||
|
switch lvl {
|
||||||
|
case logger.Warning:
|
||||||
|
l = "warning"
|
||||||
|
case logger.Debug:
|
||||||
|
l = "debug"
|
||||||
|
case logger.Info:
|
||||||
|
l = "info"
|
||||||
|
case logger.Error:
|
||||||
|
l = "error"
|
||||||
|
case logger.Fatal:
|
||||||
|
l = "fatal"
|
||||||
|
}
|
||||||
|
msg = l + ": " + msg
|
||||||
|
for i := 0; i < len(args); i++ {
|
||||||
|
msg += " %v"
|
||||||
|
}
|
||||||
|
if len(args) == 0 {
|
||||||
|
dl.Log(msg + "\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dl.Logf(msg+"\n", args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testServer(quit chan struct{}, t *testing.T) {
|
// TestReceiveAndSend tests basic RTCP client behaviour with a basic RTCP server.
|
||||||
const testServerAddr = "localhost:8000"
|
// The RTCP client will send through receiver reports, and the RTCP server will
|
||||||
sAddr, err := net.ResolveUDPAddr("udp", testServerAddr)
|
// respond with sender reports.
|
||||||
|
func TestReceiveAndSend(t *testing.T) {
|
||||||
|
const clientAddr, serverAddr = "localhost:8000", "localhost:8001"
|
||||||
|
c, err := NewClient(
|
||||||
|
clientAddr,
|
||||||
|
serverAddr,
|
||||||
|
"testClient",
|
||||||
|
10*time.Millisecond,
|
||||||
|
12345,
|
||||||
|
(*dummyLogger)(t).log,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error when creating client: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case err := <-c.ErrChan:
|
||||||
|
const errConnClosed = "use of closed network connection"
|
||||||
|
if !strings.Contains(err.Error(), errConnClosed) {
|
||||||
|
t.Fatalf("error received from client error chan: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.Start()
|
||||||
|
|
||||||
|
sAddr, err := net.ResolveUDPAddr("udp", serverAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not resolve test server address, failed with error: %v", err)
|
t.Fatalf("could not resolve test server address, failed with error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := net.DialUDP("udp", nil, sAddr)
|
cAddr, err := net.ResolveUDPAddr("udp", clientAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not dial, failed with error: %v", err)
|
t.Fatalf("could not resolve client address, failed with error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
conn, err := net.DialUDP("udp", sAddr, cAddr)
|
||||||
case <-quit:
|
if err != nil {
|
||||||
return
|
t.Fatalf("could not dial, failed with error: %v\n", err)
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 4096)
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
t.Log("SERVER: waiting for receiver report\n")
|
||||||
|
n, _, _ := conn.ReadFromUDP(buf)
|
||||||
|
t.Logf("SERVER: receiver report received: \n%v\n", buf[:n])
|
||||||
|
|
||||||
|
c.UpdateSequence(uint32(i))
|
||||||
|
|
||||||
|
now := time.Now().Second()
|
||||||
|
var time [8]byte
|
||||||
|
binary.BigEndian.PutUint64(time[:], uint64(now))
|
||||||
|
msw := binary.BigEndian.Uint32(time[:])
|
||||||
|
lsw := binary.BigEndian.Uint32(time[4:])
|
||||||
|
|
||||||
|
report := SenderReport{
|
||||||
|
Header: Header{
|
||||||
|
Version: 2,
|
||||||
|
Padding: false,
|
||||||
|
ReportCount: 0,
|
||||||
|
Type: typeSenderReport,
|
||||||
|
},
|
||||||
|
SSRC: 1234567,
|
||||||
|
TimestampMSW: msw,
|
||||||
|
TimestampLSW: lsw,
|
||||||
|
RTPTimestamp: 0,
|
||||||
|
PacketCount: 0,
|
||||||
|
OctetCount: 0,
|
||||||
|
}
|
||||||
|
r := report.Bytes()
|
||||||
|
t.Logf("SERVER: sending sender report: \n%v\n", r)
|
||||||
|
_, err := conn.Write(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("did not expect error: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
|
@ -9,12 +9,15 @@ import (
|
||||||
// significant word, and the least significant word. If the given bytes do not
|
// significant word, and the least significant word. If the given bytes do not
|
||||||
// represent a valid receiver report, an error is returned.
|
// represent a valid receiver report, an error is returned.
|
||||||
func Timestamp(buf []byte) (msw, lsw uint32, err error) {
|
func Timestamp(buf []byte) (msw, lsw uint32, err error) {
|
||||||
|
if len(buf) < 4 {
|
||||||
|
return 0, 0, errors.New("bad RTCP packet, not of sufficient length")
|
||||||
|
}
|
||||||
if (buf[0] & 0xc0 >> 6) != 2 {
|
if (buf[0] & 0xc0 >> 6) != 2 {
|
||||||
return 0, 0, errors.New("incompatible RTCP version")
|
return 0, 0, errors.New("incompatible RTCP version")
|
||||||
}
|
}
|
||||||
|
|
||||||
if buf[1] != typeSenderReport {
|
if buf[1] != typeSenderReport {
|
||||||
return 0, 0, errors.New("rtcp packet is not of sender report type")
|
return 0, 0, errors.New("RTCP packet is not of sender report type")
|
||||||
}
|
}
|
||||||
|
|
||||||
msw = binary.BigEndian.Uint32(buf[8:])
|
msw = binary.BigEndian.Uint32(buf[8:])
|
||||||
|
|
|
@ -149,7 +149,7 @@ func (r *SenderReport) Bytes() []byte {
|
||||||
r.PacketCount,
|
r.PacketCount,
|
||||||
r.OctetCount,
|
r.OctetCount,
|
||||||
} {
|
} {
|
||||||
binary.BigEndian.PutUint32(buf[i+1:], w)
|
binary.BigEndian.PutUint32(buf[i+4:], w)
|
||||||
}
|
}
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue