Merged in rtsp-protocol (pull request #185)

protocol: added basic RTCP protocol client implementation.

Approved-by: Alan Noble <anoble@gmail.com>
Approved-by: kortschak <dan@kortschak.io>
This commit is contained in:
Saxon Milton 2019-05-09 03:06:37 +00:00
commit a5e40472d4
6 changed files with 979 additions and 0 deletions

294
protocol/rtcp/client.go Normal file
View File

@ -0,0 +1,294 @@
/*
NAME
client.go
DESCRIPTION
Client.go provides an implemntation of a basic RTCP Client that will send
receiver reports, and receive sender reports to parse relevant statistics.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
This is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
in gpl.txt. If not, see http://www.gnu.org/licenses.
*/
package rtcp
import (
"encoding/binary"
"errors"
"fmt"
"math"
"net"
"sync"
"time"
"bitbucket.org/ausocean/utils/logger"
)
const (
senderSSRC = 1 // Any non-zero value will do.
defaultClientName = "Client"
delayUnit = 1.0 / 65536.0
pkg = "rtcp: "
rtcpVer = 2
receiverBufSize = 200
)
// Log describes a function signature required by the RTCP for the purpose of
// logging.
type Log func(lvl int8, msg string, args ...interface{})
// Client is an RTCP Client that will handle receiving SenderReports from a server
// and sending out ReceiverReports.
type Client struct {
cAddr *net.UDPAddr // Address of client.
sAddr *net.UDPAddr // Address of RTSP server.
name string // Name of the client for source description purposes.
sourceSSRC uint32 // Source identifier of this client.
mu sync.Mutex // Will be used to change parameters during operation safely.
seq uint32 // Last RTP sequence number.
senderTs [8]byte // The timestamp of the last sender report.
interval time.Duration // Interval between sender report and receiver report.
receiveTime time.Time // Time last sender report was received.
buf [receiverBufSize]byte // Buf used to store the receiver report and source descriptions.
conn *net.UDPConn // The UDP connection used for receiving and sending RTSP packets.
wg sync.WaitGroup // This is used to wait for send and recv routines to stop when Client is stopped.
quit chan struct{} // Channel used to communicate quit signal to send and recv routines.
log Log // Used to log any messages.
err chan error // Client will send any errors through this chan. Can be accessed by Err().
}
// NewClient returns a pointer to a new Client.
func NewClient(clientAddress, serverAddress, name string, sendInterval time.Duration, rtpSSRC uint32, l Log) (*Client, error) {
if name == "" {
name = defaultClientName
}
c := &Client{
name: name,
err: make(chan error),
quit: make(chan struct{}),
interval: sendInterval,
sourceSSRC: rtpSSRC,
log: l,
}
var err error
c.cAddr, err = net.ResolveUDPAddr("udp", clientAddress)
if err != nil {
return nil, errors.New(fmt.Sprintf("can't resolve Client address, failed with error: %v\n", err))
}
c.sAddr, err = net.ResolveUDPAddr("udp", serverAddress)
if err != nil {
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
}
// Start starts the listen and send routines. This will start the process of
// receiving and parsing sender reports, and the process of sending receiver
// reports to the server.
func (c *Client) Start() {
c.log(logger.Debug, pkg+"Client is starting")
c.wg.Add(2)
go c.recv()
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.quit)
c.conn.Close()
c.wg.Wait()
}
// Err provides read access to the Client err channel. This must be checked
// otherwise the client will block if an error encountered.
func (c *Client) Err() <-chan error {
return c.err
}
// recv reads from the UDP connection and parses SenderReports.
func (c *Client) recv() {
defer c.wg.Done()
c.log(logger.Debug, pkg+"Client is receiving")
buf := make([]byte, 4096)
for {
select {
case <-c.quit:
return
default:
n, _, err := c.conn.ReadFromUDP(buf)
if err != nil {
c.err <- err
continue
}
c.log(logger.Debug, pkg+"sender report received", "report", buf[:n])
c.parse(buf[:n])
}
}
}
// send writes receiver reports to the server.
func (c *Client) send() {
defer c.wg.Done()
c.log(logger.Debug, pkg+"Client is sending")
for {
select {
case <-c.quit:
return
default:
time.Sleep(c.interval)
report := ReceiverReport{
Header: Header{
Version: rtcpVer,
Padding: false,
ReportCount: 1,
Type: typeReceiverReport,
},
SenderSSRC: senderSSRC,
Blocks: []ReportBlock{
ReportBlock{
SourceIdentifier: c.sourceSSRC,
FractionLost: 0,
PacketsLost: math.MaxUint32,
HighestSequence: c.sequence(),
Jitter: c.jitter(),
SenderReportTs: c.lastSenderTs(),
SenderReportDelay: c.delay(),
},
},
Extensions: nil,
}
description := Description{
Header: Header{
Version: rtcpVer,
Padding: false,
ReportCount: 1,
Type: typeDescription,
},
Chunks: []Chunk{
Chunk{
SSRC: senderSSRC,
Items: []SDESItem{
SDESItem{
Type: typeCName,
Text: []byte(c.name),
},
},
},
},
}
c.log(logger.Debug, pkg+"sending receiver report")
_, err := c.conn.Write(c.formPayload(&report, &description))
if err != nil {
c.err <- err
}
}
}
}
// formPayload takes a pointer to a ReceiverReport and a pointer to a
// Source Description and calls Bytes on both, writing to the underlying Client
// buf. A slice to the combined writtem memory is returned.
func (c *Client) formPayload(r *ReceiverReport, d *Description) []byte {
rl := len(r.Bytes(c.buf[:]))
dl := len(d.Bytes(c.buf[rl:]))
t := rl + dl
if t > cap(c.buf) {
panic("Client buf not big enough")
}
return c.buf[:t]
}
// parse will read important statistics from sender reports.
func (c *Client) parse(buf []byte) {
c.markReceivedTime()
t, err := ParseTimestamp(buf)
if err != nil {
c.err <- fmt.Errorf("could not get timestamp from sender report, failed with error: %v", err)
}
c.setSenderTs(t)
}
// SetSequence will allow updating of the highest sequence number received
// through an RTP stream.
func (c *Client) SetSequence(s uint32) {
c.mu.Lock()
c.seq = s
c.mu.Unlock()
}
// sequence will return the highest sequence number received through RTP.
func (c *Client) sequence() uint32 {
c.mu.Lock()
s := c.seq
c.mu.Unlock()
return s
}
// jitter returns the interarrival jitter as described by RTCP specifications:
// https://tools.ietf.org/html/rfc3550
// TODO(saxon): complete this.
func (c *Client) jitter() uint32 {
return 0
}
// setSenderTs allows us to safely set the current sender report timestamp.
func (c *Client) setSenderTs(t Timestamp) {
c.mu.Lock()
binary.BigEndian.PutUint32(c.senderTs[:], t.Seconds)
binary.BigEndian.PutUint32(c.senderTs[4:], t.Fraction)
c.mu.Unlock()
}
// lastSenderTs returns the timestamp of the most recent sender report.
func (c *Client) lastSenderTs() uint32 {
c.mu.Lock()
t := binary.BigEndian.Uint32(c.senderTs[2:])
c.mu.Unlock()
return t
}
// delay returns the duration between the receive time of the last sender report
// and now. This is called when forming a receiver report.
func (c *Client) delay() uint32 {
c.mu.Lock()
t := c.receiveTime
c.mu.Unlock()
return uint32(time.Now().Sub(t).Seconds() / delayUnit)
}
// markReceivedTime is called when a sender report is received to mark the receive time.
func (c *Client) markReceivedTime() {
c.mu.Lock()
c.receiveTime = time.Now()
c.mu.Unlock()
}

View File

@ -0,0 +1,230 @@
/*
NAME
client_test.go
DESCRIPTION
client_test.go contains testing utilities for functionality provided in client.go.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
This is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
in gpl.txt. If not, see http://www.gnu.org/licenses.
*/
package rtcp
import (
"bytes"
"encoding/binary"
"fmt"
"math"
"net"
"strings"
"testing"
"time"
"bitbucket.org/ausocean/utils/logger"
)
// TestFromPayload checks that formPayload is working as expected.
func TestFormPayload(t *testing.T) {
// Expected data from a valid RTCP packet.
expect := []byte{
0x81, 0xc9, 0x00, 0x07,
0xd6, 0xe0, 0x98, 0xda,
0x6f, 0xad, 0x40, 0xc6,
0x00, 0xff, 0xff, 0xff,
0x00, 0x01, 0x83, 0x08,
0x00, 0x00, 0x00, 0x20,
0xb9, 0xe1, 0x25, 0x2a,
0x00, 0x00, 0x2b, 0xf9,
0x81, 0xca, 0x00, 0x04,
0xd6, 0xe0, 0x98, 0xda,
0x01, 0x08, 0x73, 0x61,
0x78, 0x6f, 0x6e, 0x2d,
0x70, 0x63, 0x00, 0x00,
}
report := ReceiverReport{
Header: Header{
Version: 2,
Padding: false,
ReportCount: 1,
Type: typeReceiverReport,
},
SenderSSRC: 3605043418,
Blocks: []ReportBlock{
ReportBlock{
SourceIdentifier: 1873625286,
FractionLost: 0,
PacketsLost: math.MaxUint32,
HighestSequence: 99080,
Jitter: 32,
SenderReportTs: 3118540074,
SenderReportDelay: 11257,
},
},
Extensions: nil,
}
description := Description{
Header: Header{
Version: 2,
Padding: false,
ReportCount: 1,
Type: typeDescription,
},
Chunks: []Chunk{
Chunk{
SSRC: 3605043418,
Items: []SDESItem{
SDESItem{
Type: typeCName,
Text: []byte("saxon-pc"),
},
},
},
},
}
c := &Client{}
p := c.formPayload(&report, &description)
if !bytes.Equal(p, expect) {
t.Fatalf("unexpected result.\nGot: %v\n Want: %v\n", p, expect)
}
bufAddr := fmt.Sprintf("%p", c.buf[:])
pAddr := fmt.Sprintf("%p", p)
if bufAddr != pAddr {
t.Errorf("unexpected result.\nGot: %v\n want: %v\n", pAddr, bufAddr)
}
}
// dummyLogger will allow logging to be done by the testing pkg.
type dummyLogger testing.T
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)
}
// TestReceiveAndSend tests basic RTCP client behaviour with a basic RTCP server.
// The RTCP client will send through receiver reports, and the RTCP server will
// 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.Err():
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 {
t.Fatalf("could not resolve test server address, failed with error: %v", err)
}
cAddr, err := net.ResolveUDPAddr("udp", clientAddr)
if err != nil {
t.Fatalf("could not resolve client address, failed with error: %v", err)
}
conn, err := net.DialUDP("udp", sAddr, cAddr)
if err != nil {
t.Fatalf("could not dial, failed with error: %v\n", err)
}
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.SetSequence(uint32(i))
now := time.Now().Second()
var time [8]byte
binary.BigEndian.PutUint64(time[:], uint64(now))
msw := binary.BigEndian.Uint32(time[:4])
lsw := binary.BigEndian.Uint32(time[4:])
report := SenderReport{
Header: Header{
Version: rtcpVer,
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()
}

60
protocol/rtcp/parse.go Normal file
View File

@ -0,0 +1,60 @@
/*
NAME
parse.go
DESCRIPTION
parse.go contains functionality for parsing RTCP packets.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
This is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
in gpl.txt. If not, see http://www.gnu.org/licenses.
*/
package rtcp
import (
"encoding/binary"
"errors"
)
// Timestamp describes an NTP timestamp, see https://tools.ietf.org/html/rfc1305
type Timestamp struct {
Seconds uint32
Fraction uint32
}
// Timestamp gets the timestamp from a receiver report and returns it as the most
// significant word, and the least significant word. If the given bytes do not
// represent a valid receiver report, an error is returned.
func ParseTimestamp(buf []byte) (Timestamp, error) {
if len(buf) < 4 {
return Timestamp{}, errors.New("bad RTCP packet, not of sufficient length")
}
if (buf[0]&0xc0)>>6 != rtcpVer {
return Timestamp{}, errors.New("incompatible RTCP version")
}
if buf[1] != typeSenderReport {
return Timestamp{}, errors.New("RTCP packet is not of sender report type")
}
return Timestamp{
Seconds: binary.BigEndian.Uint32(buf[8:]),
Fraction: binary.BigEndian.Uint32(buf[12:]),
}, nil
}

View File

@ -0,0 +1,61 @@
/*
NAME
parse_test.go
DESCRIPTION
parse_test.go provides testing utilities for functionality found in parse.go.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
This is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
in gpl.txt. If not, see http://www.gnu.org/licenses.
*/
package rtcp
import (
"testing"
)
// TestTimestamp checks that Timestamp correctly returns the most signicicant
// word, and least signiciant word, of a receiver report timestamp.
func TestTimestamp(t *testing.T) {
const expectedMSW = 2209003992
const expectedLSW = 1956821460
report := []byte{
0x80, 0xc8, 0x00, 0x06,
0x6f, 0xad, 0x40, 0xc6,
0x83, 0xaa, 0xb9, 0xd8, // Most significant word of timestamp (2209003992)
0x74, 0xa2, 0xb9, 0xd4, // Least significant word of timestamp (1956821460)
0x4b, 0x1c, 0x5a, 0xa5,
0x00, 0x00, 0x00, 0x66,
0x00, 0x01, 0xc2, 0xc5,
}
ts, err := ParseTimestamp(report)
if err != nil {
t.Fatalf("did not expect error: %v", err)
}
if ts.Seconds != expectedMSW {
t.Errorf("most significant word of timestamp is not what's expected. \nGot: %v\n Want: %v\n", ts.Seconds, expectedMSW)
}
if ts.Fraction != expectedLSW {
t.Errorf("least significant word of timestamp is not what's expected. \nGot: %v\n Want: %v\n", ts.Fraction, expectedLSW)
}
}

222
protocol/rtcp/rtcp.go Normal file
View File

@ -0,0 +1,222 @@
/*
NAME
rtcp.go
DESCRIPTION
rtcp.go contains structs to describe RTCP packets, and functionality to form
[]bytes of these structs.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
This is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
in gpl.txt. If not, see http://www.gnu.org/licenses.
*/
// Package RTCP provides RTCP data structures and a client for communicating
// with an RTCP service.
package rtcp
import (
"encoding/binary"
)
// RTCP packet types.
const (
typeSenderReport = 200
typeReceiverReport = 201
typeDescription = 202
)
// Source Description Item types.
const (
typeCName = 1
)
const (
reportBlockSize = 6
senderReportSize = 28
)
// ReceiverReport describes an RTCP receiver report packet.
type ReceiverReport struct {
Header // Standard RTCP packet header.
SenderSSRC uint32 // SSRC of the sender of this report.
Blocks []ReportBlock // Report blocks.
Extensions [][4]byte // Contains any extensions to the packet.
}
// Bytes returns a []byte of the ReceiverReport r.
func (r *ReceiverReport) Bytes(buf []byte) []byte {
l := 8 + 4*reportBlockSize*len(r.Blocks) + 4*len(r.Extensions)
if buf == nil || cap(buf) < l {
buf = make([]byte, l)
}
buf = buf[:l]
l = 1 + reportBlockSize*len(r.Blocks) + len(r.Extensions)
r.writeHeader(buf, l)
binary.BigEndian.PutUint32(buf[4:], r.SenderSSRC)
idx := 8
for _, b := range r.Blocks {
binary.BigEndian.PutUint32(buf[idx:], b.SourceIdentifier)
binary.BigEndian.PutUint32(buf[idx+4:], b.PacketsLost)
buf[idx+4] = b.FractionLost
binary.BigEndian.PutUint32(buf[idx+8:], b.HighestSequence)
binary.BigEndian.PutUint32(buf[idx+12:], b.Jitter)
binary.BigEndian.PutUint32(buf[idx+16:], b.SenderReportTs)
binary.BigEndian.PutUint32(buf[idx+20:], b.SenderReportDelay)
idx += 24
}
for _, e := range r.Extensions {
copy(buf[idx:], e[:])
idx += 4
}
return buf
}
// ReportBlock describes an RTCP report block used in Sender/Receiver Reports.
type ReportBlock struct {
SourceIdentifier uint32 // Source identifier.
FractionLost uint8 // Fraction of packets lost.
PacketsLost uint32 // Cumulative number of packets lost.
HighestSequence uint32 // Extended highest sequence number received.
Jitter uint32 // Interarrival jitter.
SenderReportTs uint32 // Last sender report timestamp.
SenderReportDelay uint32 // Delay since last sender report.
}
// Description describes a source description RTCP packet.
type Description struct {
Header // Standard RTCP packet header.
Chunks []Chunk // Chunks to describe items of each SSRC.
}
// Bytes returns an []byte of the Description d.
func (d *Description) Bytes(buf []byte) []byte {
bodyLen := d.bodyLen()
rem := bodyLen % 4
if rem != 0 {
bodyLen += 4 - rem
}
l := 4 + bodyLen
if buf == nil || cap(buf) < l {
buf = make([]byte, l)
}
buf = buf[:l]
d.writeHeader(buf, bodyLen/4)
idx := 4
for _, c := range d.Chunks {
binary.BigEndian.PutUint32(buf[idx:], c.SSRC)
idx += 4
for _, i := range c.Items {
buf[idx] = i.Type
buf[idx+1] = byte(len(i.Text))
idx += 2
copy(buf[idx:], i.Text)
idx += len(i.Text)
}
}
return buf
}
// bodyLen calculates the body length of a source description packet in bytes.
func (d *Description) bodyLen() int {
var l int
for _, c := range d.Chunks {
l += c.len()
}
return l
}
// SenderReport describes an RTCP sender report.
type SenderReport struct {
Header // Standard RTCP header.
SSRC uint32 // SSRC of sender.
TimestampMSW uint32 // Most significant word of timestamp.
TimestampLSW uint32 // Least significant word of timestamp.
RTPTimestamp uint32 // Current RTP timestamp.
PacketCount uint32 // Senders packet count.
OctetCount uint32 // Senders octet count.
// Report blocks (unimplemented)
// ...
}
// Bytes returns a []byte of the SenderReport.
func (r *SenderReport) Bytes() []byte {
buf := make([]byte, senderReportSize)
r.writeHeader(buf, senderReportSize-1)
for i, w := range []uint32{
r.SSRC,
r.TimestampMSW,
r.TimestampLSW,
r.RTPTimestamp,
r.PacketCount,
r.OctetCount,
} {
binary.BigEndian.PutUint32(buf[i+4:], w)
}
return buf
}
// Header describes a standard RTCP packet header.
type Header struct {
Version uint8 // RTCP version.
Padding bool // Padding indicator.
ReportCount uint8 // Number of reports contained.
Type uint8 // Type of RTCP packet.
}
// SDESItem describes a source description item.
type SDESItem struct {
Type uint8 // Type of item.
Text []byte // Item text.
}
// Chunk describes a source description chunk for a given SSRC.
type Chunk struct {
SSRC uint32 // SSRC of the source being described by the below items.
Items []SDESItem // Items describing the source.
}
// len returns the len of a chunk in bytes.
func (c *Chunk) len() int {
tot := 4
for _, i := range c.Items {
tot += 2 + len(i.Text)
}
return tot
}
// writeHeader writes the standard RTCP header given a buffer to write to and l
// the RTCP body length that needs to be encoded into the header.
func (h Header) writeHeader(buf []byte, l int) {
buf[0] = h.Version<<6 | asByte(h.Padding)<<5 | 0x1f&h.ReportCount
buf[1] = h.Type
binary.BigEndian.PutUint16(buf[2:], uint16(l))
}
func asByte(b bool) byte {
if b {
return 0x01
}
return 0x00
}

112
protocol/rtcp/rtcp_test.go Normal file
View File

@ -0,0 +1,112 @@
/*
NAME
rtcp_test.go
DESCRIPTION
rtcp_test.go contains testing utilities for functionality provided in rtcp_test.go.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
This is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
in gpl.txt. If not, see http://www.gnu.org/licenses.
*/
package rtcp
import (
"bytes"
"math"
"testing"
)
// TestReceiverReportBytes checks that we can correctly obtain a []byte of an
// RTCP receiver report from the struct representation.
func TestReceiverReportBytes(t *testing.T) {
expect := []byte{
0x81, 0xc9, 0x00, 0x07,
0xd6, 0xe0, 0x98, 0xda,
0x6f, 0xad, 0x40, 0xc6,
0x00, 0xff, 0xff, 0xff,
0x00, 0x01, 0x83, 0x08,
0x00, 0x00, 0x00, 0x20,
0xb9, 0xe1, 0x25, 0x2a,
0x00, 0x00, 0x2b, 0xf9,
}
report := ReceiverReport{
Header: Header{
Version: 2,
Padding: false,
ReportCount: 1,
Type: typeReceiverReport,
},
SenderSSRC: 3605043418,
Blocks: []ReportBlock{
ReportBlock{
SourceIdentifier: 1873625286,
FractionLost: 0,
PacketsLost: math.MaxUint32,
HighestSequence: 99080,
Jitter: 32,
SenderReportTs: 3118540074,
SenderReportDelay: 11257,
},
},
Extensions: nil,
}
got := report.Bytes(nil)
if !bytes.Equal(got, expect) {
t.Errorf("did not get expected result. \nGot: %v\nWant: %v\n", got, expect)
}
}
// TestSourceDescriptionBytes checks that we can correctly obtain a []byte of an
// RTCP source description from the struct representation.
func TestSourceDescriptionBytes(t *testing.T) {
expect := []byte{
0x81, 0xca, 0x00, 0x04,
0xd6, 0xe0, 0x98, 0xda,
0x01, 0x08, 0x73, 0x61,
0x78, 0x6f, 0x6e, 0x2d,
0x70, 0x63, 0x00, 0x00,
}
description := Description{
Header: Header{
Version: 2,
Padding: false,
ReportCount: 1,
Type: typeDescription,
},
Chunks: []Chunk{
Chunk{
SSRC: 3605043418,
Items: []SDESItem{
SDESItem{
Type: typeCName,
Text: []byte("saxon-pc"),
},
},
},
},
}
got := description.Bytes(nil)
if !bytes.Equal(got, expect) {
t.Errorf("Did not get expected result.\nGot: %v\n Want: %v\n", got, expect)
}
}