diff --git a/protocol/rtp/parse.go b/protocol/rtp/parse.go new file mode 100644 index 00000000..d658aa20 --- /dev/null +++ b/protocol/rtp/parse.go @@ -0,0 +1,67 @@ +/* +NAME + parse.go + +DESCRIPTION + parse.go provides functionality for parsing RTP packets. + +AUTHOR + Saxon A. Nelson-Milton + +LICENSE + Copyright (C) 2019 the Australian Ocean Lab (AusOcean) + + This 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 rtp + +import ( + "encoding/binary" + "errors" +) + +const badVer = "incompatible RTP version" + +// Payload returns the payload from an RTP packet provided the version is +// compatible, otherwise an error is returned. +func Payload(d []byte) ([]byte, error) { + if len(d) < defaultHeadSize { + panic("invalid RTP packet length") + } + if version(d) != rtpVer { + return nil, errors.New(badVer) + } + extLen := 0 + if hasExt(d) { + extLen = 4 + 4*(int(binary.BigEndian.Uint16(d[optionalFieldIdx+4*csrcCount(d)+2:]))) + } + payloadIdx := optionalFieldIdx + 4*csrcCount(d) + extLen + return d[payloadIdx:], nil +} + +// hasExt returns true if an extension is present in the RTP packet. +func hasExt(d []byte) bool { + return (d[0] & 0x10 >> 4) == 1 +} + +// csrcCount returns the number of CSRC fields. +func csrcCount(d []byte) int { + return int(d[0] & 0x0f) +} + +// version returns the version of the RTP packet. +func version(d []byte) int { + return int(d[0] & 0xc0 >> 6) +} diff --git a/protocol/rtp/parse_test.go b/protocol/rtp/parse_test.go new file mode 100644 index 00000000..f3468c57 --- /dev/null +++ b/protocol/rtp/parse_test.go @@ -0,0 +1,140 @@ +/* +NAME + parse_test.go + +DESCRIPTION + parse_test.go provides testing for behaviour of functionality in parse.go. + +AUTHOR + Saxon A. Nelson-Milton + +LICENSE + Copyright (C) 2019 the Australian Ocean Lab (AusOcean) + + This 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 rtp + +import ( + "bytes" + "testing" +) + +// TestVersion checks that we can correctly get the version from an RTP packet. +func TestVersion(t *testing.T) { + const expect = 1 + got := version((&Pkt{V: expect}).Bytes(nil)) + if got != expect { + t.Errorf("unexpected version for RTP packet. Got: %v\n Want: %v\n", got, expect) + } +} + +// TestCsrcCount checks that we can correctly obtain the csrc count from an +// RTP packet. +func TestCsrcCount(t *testing.T) { + const ver, expect = 2, 2 + + pkt := (&Pkt{ + V: ver, + CC: expect, + CSRC: make([][4]byte, expect), + }).Bytes(nil) + + got := csrcCount(pkt) + if got != expect { + t.Errorf("unexpected csrc count for RTP packet. Got: %v\n Want: %v\n", got, expect) + } +} + +// TestHasExt checks the behaviour of hasExt with an RTP packet that has the +// extension indicator true, and one with the extension indicator set to false. +func TestHasExt(t *testing.T) { + const ver = 2 + + // First check for when there is an extension field. + pkt := &Pkt{ + V: ver, + X: true, + Extension: ExtensionHeader{ + ID: 0, + Header: make([][4]byte, 0), + }, + } + + got := hasExt(pkt.Bytes(nil)) + if !got { + t.Error("RTP packet did not have true extension indicator as expected") + } + + // Now check when there is not an extension field. + pkt.X = false + got = hasExt(pkt.Bytes(nil)) + if got { + t.Error("did not expect to have extension indicator as true") + } +} + +// TestPayload checks that we can correctly get the payload of an RTP packet +// using Payload for a variety of RTP packet configurations. +func TestPayload(t *testing.T) { + const ver = 2 + expect := []byte{0x01, 0x02, 0x03, 0x04, 0x05} + + testPkts := [][]byte{ + (&Pkt{ + V: ver, + Payload: expect, + }).Bytes(nil), + + (&Pkt{ + V: ver, + CC: 3, + CSRC: make([][4]byte, 3), + Payload: expect, + }).Bytes(nil), + + (&Pkt{ + V: ver, + X: true, + Extension: ExtensionHeader{ + ID: 0, + Header: make([][4]byte, 3), + }, + Payload: expect, + }).Bytes(nil), + + (&Pkt{ + V: ver, + CC: 3, + CSRC: make([][4]byte, 3), + Extension: ExtensionHeader{ + ID: 0, + Header: make([][4]byte, 3), + }, + Payload: expect, + }).Bytes(nil), + } + + for i, p := range testPkts { + got, err := Payload(p) + if err != nil { + t.Errorf("unexpected error from Payload with pkt: %v", i) + } + + if !bytes.Equal(got, expect) { + t.Errorf("unexpected payload data from RTP packet: %v.\n Got: %v\n Want: %v\n", i, got, expect) + } + } +} diff --git a/protocol/rtp/rtp.go b/protocol/rtp/rtp.go index 33a3e7f8..73f6f15b 100644 --- a/protocol/rtp/rtp.go +++ b/protocol/rtp/rtp.go @@ -37,10 +37,11 @@ import ( ) const ( - rtpVer = 2 - defaultHeadSize = 3 * 4 // Header size of an rtp packet. - defPayloadSize = sendSize // Default payload size for the rtp packet. - defPktSize = defaultHeadSize + defPayloadSize // Default packet size is header size + payload size. + rtpVer = 2 // Version of RTP that this package is compatible with. + defaultHeadSize = 12 // Header size of an rtp packet. + defPayloadSize = sendSize // Default payload size for the rtp packet. + defPktSize = defaultHeadSize + defPayloadSize // Default packet size is header size + payload size. + optionalFieldIdx = 12 // This is the idx of optional fields including CSRC and extension header in an RTP packet. ) // Pkt provides fields consistent with RFC3550 definition of an rtp packet