Repository: pion/rtp
Pull request #56 (2020-05-11 19:14:42)

RFC 5285 header extension support
An initial implementation of RFC 5285 header extensions based on previous work in #17. Please provide feedback :)

This is a breaking change unfortunately. We could probably avoid that with some added complexity if we'd like to. 

codecov[bot]: # [Codecov](https://codecov.io/gh/pion/rtp/pull/56?src=pr&el=h1) Report
> Merging [#56](https://codecov.io/gh/pion/rtp/pull/56?src=pr&el=desc) into [master](https://codecov.io/gh/pion/rtp/commit/f7fc346ce7b76cbb967067cb94f1eecc5b717e7b&el=desc) will **decrease** coverage by `2.89%`.
> The diff coverage is `61.76%`.

[![Impacted file tree graph](https://codecov.io/gh/pion/rtp/pull/56/graphs/tree.svg?width=650&height=150&src=pr&token=4KrbiBsspj)](https://codecov.io/gh/pion/rtp/pull/56?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #56      +/-   ##
==========================================
- Coverage   87.78%   84.89%   -2.90%     
==========================================
  Files          13       13              
  Lines         524      576      +52     
==========================================
+ Hits          460      489      +29     
- Misses         50       70      +20     
- Partials       14       17       +3     
```

| Flag | Coverage Δ | |
|---|---|---|
| #go | `84.89% <61.76%> (-2.90%)` | :arrow_down: |
| #wasm | `84.89% <61.76%> (-2.90%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/pion/rtp/pull/56?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [packetizer.go](https://codecov.io/gh/pion/rtp/pull/56/diff?src=pr&el=tree#diff-cGFja2V0aXplci5nbw==) | `66.66% <0.00%> (ø)` | |
| [packet.go](https://codecov.io/gh/pion/rtp/pull/56/diff?src=pr&el=tree#diff-cGFja2V0Lmdv) | `66.02% <64.61%> (-5.13%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pion/rtp/pull/56?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pion/rtp/pull/56?src=pr&el=footer). Last update [f7fc346...b10b1d1](https://codecov.io/gh/pion/rtp/pull/56?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

tarrencev: Not sure why the test is failing on `Test WASM 1.13`
adwpc: 
> 不知道为什么测试失败 `Test WASM 1.13`
FAIL
402The command "GOOS=js GOARCH=wasm go test \
403  -coverpkg=${coverpkgs} -coverprofile=cover.out -covermode=atomic \
404  -exec="${GO_JS_WASM_EXEC}" \
405  -v ${testpkgs}
406" exited with 1.

adwpc: LGTM！
at-wat: ```
=== RUN   TestRFC8285MultipleExtensions
--- FAIL: TestRFC8285MultipleExtensions (0.00s)
    packet_test.go:238: Marshal failed raw 
        Marshaled: [144 224 105 143 217 194 147 218 28 100 39 130 190 222 0 3 51 204 204 204 204 16 170 33 187 187 152 54 190 136 158],
        rawPkt:     [144 224 105 143 217 194 147 218 28 100 39 130 190 222 0 3 16 170 33 187 187 51 204 204 204 204 152 54 190 136 158]
```
https://travis-ci.org/github/pion/rtp/jobs/685901555#L276
tarrencev: I think I fixed the issue and the test didn't run with the new code. I can try trigger it again. Also, the code coverage is better now but that plugin is also having issues. If you click through to the code coverage page you can see. 

@Sean-Der how would you like to handle this PR since it is a breaking change?
tarrencev: The reason the tests are flaky is the iteration order of a map is not guaranteed. So i think we should switch Extensions to use an array instead.
tarrencev: I've added back `ExtensionPayload` for RFC3550 extensions. This makes these changes backward compatible. Hopefully that means we can merge them into master 😁 
tarrencev: I think this is good to go!

Patch:
diff --git a/packet.go b/packet.go
index 43c1183..3cc6ba1 100644
--- a/packet.go
+++ b/packet.go
@@ -6,6 +6,12 @@ import (
 	"io"
 )
 
+// Extension RTP Header extension
+type Extension struct {
+	id      uint8
+	payload []byte
+}
+
 // TODO(@kixelated) Remove Header.PayloadOffset and Packet.Raw
 
 // Header represents an RTP packet header
@@ -22,7 +28,7 @@ type Header struct {
 	SSRC             uint32
 	CSRC             []uint32
 	ExtensionProfile uint16
-	ExtensionPayload []byte
+	Extensions       []Extension
 }
 
 // Packet represents an RTP Packet
@@ -34,25 +40,28 @@ type Packet struct {
 }
 
 const (
-	headerLength    = 4
-	versionShift    = 6
-	versionMask     = 0x3
-	paddingShift    = 5
-	paddingMask     = 0x1
-	extensionShift  = 4
-	extensionMask   = 0x1
-	ccMask          = 0xF
-	markerShift     = 7
-	markerMask      = 0x1
-	ptMask          = 0x7F
-	seqNumOffset    = 2
-	seqNumLength    = 2
-	timestampOffset = 4
-	timestampLength = 4
-	ssrcOffset      = 8
-	ssrcLength      = 4
-	csrcOffset      = 12
-	csrcLength      = 4
+	headerLength            = 4
+	versionShift            = 6
+	versionMask             = 0x3
+	paddingShift            = 5
+	paddingMask             = 0x1
+	extensionShift          = 4
+	extensionMask           = 0x1
+	extensionProfileOneByte = 0xBEDE
+	extensionProfileTwoByte = 0x1000
+	extensionIDReserved     = 0xF
+	ccMask                  = 0xF
+	markerShift             = 7
+	markerMask              = 0x1
+	ptMask                  = 0x7F
+	seqNumOffset            = 2
+	seqNumLength            = 2
+	timestampOffset         = 4
+	timestampLength         = 4
+	ssrcOffset              = 8
+	ssrcLength              = 4
+	csrcOffset              = 12
+	csrcLength              = 4
 )
 
 // String helps with debugging by printing packet information in a readable way
@@ -120,16 +129,72 @@ func (h *Header) Unmarshal(rawPacket []byte) error {
 
 		h.ExtensionProfile = binary.BigEndian.Uint16(rawPacket[currOffset:])
 		currOffset += 2
-		extensionLength := int(binary.BigEndian.Uint16(rawPacket[currOffset:])) * 4
-		currOffset += 2
 
-		if len(rawPacket) < currOffset+extensionLength {
-			return fmt.Errorf("RTP header size insufficient for extension length; %d < %d", len(rawPacket), currOffset+extensionLength)
+		switch h.ExtensionProfile {
+		// RFC 8285 RTP One Byte Header Extension
+		case extensionProfileOneByte:
+			extensionLength := int(binary.BigEndian.Uint16(rawPacket[currOffset:]))
+			currOffset += 2
+
+			i := 0
+			for i < extensionLength {
+				if rawPacket[currOffset] == 0x00 { // padding
+					currOffset++
+					continue
+				}
+
+				extid := rawPacket[currOffset] >> 4
+				len := int(rawPacket[currOffset]&^0xF0 + 1)
+				currOffset++
+
+				if extid == extensionIDReserved {
+					break
+				}
+
+				extension := Extension{id: extid, payload: rawPacket[currOffset : currOffset+len]}
+				h.Extensions = append(h.Extensions, extension)
+				currOffset += len
+				i++
+			}
+		// RFC 8285 RTP Two Byte Header Extension
+		case extensionProfileTwoByte:
+			extensionLength := int(binary.BigEndian.Uint16(rawPacket[currOffset:]))
+			currOffset += 2
+
+			i := 0
+			for i < extensionLength {
+				if rawPacket[currOffset] == 0x00 { // padding
+					currOffset++
+					continue
+				}
+
+				extid := rawPacket[currOffset]
+				currOffset++
+
+				len := int(rawPacket[currOffset])
+				currOffset++
+
+				extension := Extension{id: extid, payload: rawPacket[currOffset : currOffset+len]}
+				h.Extensions = append(h.Extensions, extension)
+				currOffset += len
+
+				i++
+			}
+
+		default: // RFC3550 Extension
+			extensionLength := int(binary.BigEndian.Uint16(rawPacket[currOffset:])) * 4
+			currOffset += 2
+
+			if len(rawPacket) < currOffset+extensionLength {
+				return fmt.Errorf("RTP header size insufficient for extension length; %d < %d", len(rawPacket), currOffset+extensionLength)
+			}
+
+			extension := Extension{id: 0, payload: rawPacket[currOffset : currOffset+extensionLength]}
+			h.Extensions = append(h.Extensions, extension)
+			currOffset += len(h.Extensions[0].payload)
 		}
-
-		h.ExtensionPayload = rawPacket[currOffset : currOffset+extensionLength]
-		currOffset += len(h.ExtensionPayload)
 	}
+
 	h.PayloadOffset = currOffset
 
 	return nil
@@ -211,17 +276,45 @@ func (h *Header) MarshalTo(buf []byte) (n int, err error) {
 	h.PayloadOffset = n
 
 	if h.Extension {
-		if len(h.ExtensionPayload)%4 != 0 {
-			//the payload must be in 32-bit words.
-			return 0, io.ErrShortBuffer
-		}
-		extSize := uint16(len(h.ExtensionPayload) / 4)
-
 		binary.BigEndian.PutUint16(buf[n+0:n+2], h.ExtensionProfile)
-		binary.BigEndian.PutUint16(buf[n+2:n+4], extSize)
-		n += 4
-
-		n += copy(buf[n:], h.ExtensionPayload)
+		n += 2
+
+		switch h.ExtensionProfile {
+		// RFC 8285 RTP One Byte Header Extension
+		case extensionProfileOneByte:
+			extSize := uint16(len(h.Extensions))
+			binary.BigEndian.PutUint16(buf[n:n+2], extSize)
+			n += 2
+
+			for _, extension := range h.Extensions {
+				buf[n] = extension.id<<4 | (uint8(len(extension.payload)) - 1)
+				n++
+				n += copy(buf[n:], extension.payload)
+			}
+		// RFC 8285 RTP Two Byte Header Extension
+		case extensionProfileTwoByte:
+			extSize := uint16(len(h.Extensions))
+			binary.BigEndian.PutUint16(buf[n:n+2], extSize)
+			n += 2
+
+			for _, extension := range h.Extensions {
+				buf[n] = extension.id
+				n++
+				buf[n] = uint8(len(extension.payload))
+				n++
+				n += copy(buf[n:], extension.payload)
+			}
+		default: // RFC3550 Extension
+			extlen := len(h.Extensions[0].payload)
+			if extlen%4 != 0 {
+				//the payload must be in 32-bit words.
+				return 0, io.ErrShortBuffer
+			}
+			extSize := uint16(extlen / 4)
+			binary.BigEndian.PutUint16(buf[n:n+2], extSize)
+			n += 2
+			n += copy(buf[n:], h.Extensions[0].payload)
+		}
 	}
 
 	return n, nil
@@ -233,12 +326,87 @@ func (h *Header) MarshalSize() int {
 	size := 12 + (len(h.CSRC) * csrcLength)
 
 	if h.Extension {
-		size += 4 + len(h.ExtensionPayload)
+		size += 4
+
+		switch h.ExtensionProfile {
+		// RFC 8285 RTP One Byte Header Extension
+		case extensionProfileOneByte:
+			for _, extension := range h.Extensions {
+				size += 1 + len(extension.payload)
+			}
+		// RFC 8285 RTP Two Byte Header Extension
+		case extensionProfileTwoByte:
+			for _, extension := range h.Extensions {
+				size += 2 + len(extension.payload)
+			}
+		default:
+			size += len(h.Extensions[0].payload)
+		}
 	}
-
 	return size
 }
 
+// SetExtension sets an RTP header extension
+func (h *Header) SetExtension(id uint8, payload []byte) error {
+	if h.Extension {
+		switch h.ExtensionProfile {
+		// RFC 8285 RTP One Byte Header Extension
+		case extensionProfileOneByte:
+			if id < 1 || id > 14 {
+				return fmt.Errorf("header extension id must be between 1 and 14 for RFC 5285 extensions")
+			}
+			if len(payload) > 16 {
+				return fmt.Errorf("header extension payload must be 16bytes or less for RFC 5285 one byte extensions")
+			}
+		// RFC 8285 RTP Two Byte Header Extension
+		case extensionProfileTwoByte:
+			if id < 1 || id > 255 {
+				return fmt.Errorf("header extension id must be between 1 and 14 for RFC 5285 extensions")
+			}
+			if len(payload) > 256 {
+				return fmt.Errorf("header extension payload must be 256bytes or less for RFC 5285 two byte extensions")
+			}
+		default: // RFC3550 Extension
+			if id != 0 {
+				return fmt.Errorf("header extension id must be 0 for none RFC 5285 extensions")
+			}
+		}
+
+		// Update existing if it exists else add new extension
+		for i, extension := range h.Extensions {
+			if extension.id == id {
+				h.Extensions[i].payload = payload
+				return nil
+			}
+		}
+		h.Extensions = append(h.Extensions, Extension{id: id, payload: payload})
+		return nil
+	}
+
+	// No existing header extensions
+	h.Extension = true
+
+	switch len := len(payload); {
+	case len < 16:
+		h.ExtensionProfile = extensionProfileOneByte
+	case len > 16 && len < 256:
+		h.ExtensionProfile = extensionProfileTwoByte
+	}
+
+	h.Extensions = append(h.Extensions, Extension{id: id, payload: payload})
+	return nil
+}
+
+// GetExtension returns an RTP header extension
+func (h *Header) GetExtension(id uint8) []byte {
+	for _, extension := range h.Extensions {
+		if extension.id == id {
+			return extension.payload
+		}
+	}
+	return nil
+}
+
 // Marshal serializes the packet into bytes.
 func (p *Packet) Marshal() (buf []byte, err error) {
 	buf = make([]byte, p.MarshalSize())
diff --git a/packetizer.go b/packetizer.go
index 37032b7..ccdb55a 100644
--- a/packetizer.go
+++ b/packetizer.go
@@ -81,8 +81,6 @@ func (p *packetizer) Packetize(payload []byte, samples uint32) []*Packet {
 	if len(packets) != 0 && p.extensionNumbers.AbsSendTime != 0 {
 		t := toNtpTime(p.timegen()) >> 14
 		//apply http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
-		packets[len(packets)-1].Header.Extension = true
-		packets[len(packets)-1].ExtensionProfile = 0xBEDE
 		b, err := (&AbsSendTimeExtension{
 			ID:        uint8(p.extensionNumbers.AbsSendTime),
 			Timestamp: t,
@@ -90,7 +88,10 @@ func (p *packetizer) Packetize(payload []byte, samples uint32) []*Packet {
 		if err != nil {
 			return nil // never happens
 		}
-		packets[len(packets)-1].ExtensionPayload = b
+		err = packets[len(packets)-1].SetExtension(0, b)
+		if err != nil {
+			return nil // never happens
+		}
 	}
 
 	return packets


Test patch:
diff --git a/packet_test.go b/packet_test.go
index 895f690..2c23ab4 100644
--- a/packet_test.go
+++ b/packet_test.go
@@ -22,14 +22,18 @@ func TestBasic(t *testing.T) {
 			Marker:           true,
 			Extension:        true,
 			ExtensionProfile: 1,
-			ExtensionPayload: []byte{0xFF, 0xFF, 0xFF, 0xFF},
-			Version:          2,
-			PayloadOffset:    20,
-			PayloadType:      96,
-			SequenceNumber:   27023,
-			Timestamp:        3653407706,
-			SSRC:             476325762,
-			CSRC:             []uint32{},
+			Extensions: []Extension{
+				{0, []byte{
+					0xFF, 0xFF, 0xFF, 0xFF,
+				}},
+			},
+			Version:        2,
+			PayloadOffset:  20,
+			PayloadType:    96,
+			SequenceNumber: 27023,
+			Timestamp:      3653407706,
+			SSRC:           476325762,
+			CSRC:           []uint32{},
 		},
 		Payload: rawPkt[20:],
 		Raw:     rawPkt,
@@ -86,7 +90,11 @@ func TestExtension(t *testing.T) {
 	p = &Packet{Header: Header{
 		Extension:        true,
 		ExtensionProfile: 3,
-		ExtensionPayload: []byte{0},
+		Extensions: []Extension{
+			{0, []byte{
+				0,
+			}},
+		},
 	},
 		Payload: []byte{},
 	}
@@ -95,6 +103,662 @@ func TestExtension(t *testing.T) {
 	}
 }
 
+func TestRFC8285OneByteExtension(t *testing.T) {
+	p := &Packet{}
+
+	rawPkt := []byte{
+		0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64,
+		0x27, 0x82, 0xBE, 0xDE, 0x00, 0x01, 0x50, 0xAA, 0x98, 0x36, 0xbe, 0x88, 0x9e,
+	}
+	if err := p.Unmarshal(rawPkt); err != nil {
+		t.Fatal("Unmarshal err for valid extension")
+	}
+
+	p = &Packet{Header: Header{
+		Marker:           true,
+		Extension:        true,
+		ExtensionProfile: 0xBEDE,
+		Extensions: []Extension{
+			{5, []byte{
+				0xAA,
+			}},
+		},
+		Version:        2,
+		PayloadOffset:  18,
+		PayloadType:    96,
+		SequenceNumber: 27023,
+		Timestamp:      3653407706,
+		SSRC:           476325762,
+		CSRC:           []uint32{},
+	},
+		Payload: rawPkt[18:],
+		Raw:     rawPkt,
+	}
+
+	dstData, _ := p.Marshal()
+	if !bytes.Equal(dstData, rawPkt) {
+		t.Error("Marshal failed")
+	}
+}
+
+func TestRFC8285OneByteMultipleExtensionsWithPadding(t *testing.T) {
+	p := &Packet{}
+
+	//  0                   1                   2                   3
+	//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	// |       0xBE    |    0xDE       |           length=3            |
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	// |  ID   | L=0   |     data      |  ID   |  L=1  |   data...
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	//       ...data   |    0 (pad)    |    0 (pad)    |  ID   | L=3   |
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	// |                          data                                 |
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	rawPkt := []byte{
+		0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64,
+		0x27, 0x82, 0xBE, 0xDE, 0x00, 0x03, 0x10, 0xAA, 0x21, 0xBB,
+		0xBB, 0x00, 0x00, 0x33, 0xCC, 0xCC, 0xCC, 0xCC, 0x98,
+		0x36, 0xbe, 0x88, 0x9e,
+	}
+	if err := p.Unmarshal(rawPkt); err != nil {
+		t.Fatal("Unmarshal err for valid extension")
+	}
+
+	ext1 := p.GetExtension(1)
+	ext1Expect := []byte{0xAA}
+	if !bytes.Equal(ext1, ext1Expect) {
+		t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext1, ext1Expect)
+	}
+
+	ext2 := p.GetExtension(2)
+	ext2Expect := []byte{0xBB, 0xBB}
+	if !bytes.Equal(ext2, ext2Expect) {
+		t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext2, ext2Expect)
+	}
+
+	ext3 := p.GetExtension(3)
+	ext3Expect := []byte{0xCC, 0xCC, 0xCC, 0xCC}
+	if !bytes.Equal(ext3, ext3Expect) {
+		t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext3, ext3Expect)
+	}
+}
+
+func TestRFC8285OneByteMultipleExtensions(t *testing.T) {
+	//  0                   1                   2                   3
+	//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	// |       0xBE    |    0xDE       |           length=3            |
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	// |  ID=1 | L=0   |     data      |  ID=2 |  L=1  |   data...
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	//       ...data   |  ID=3 | L=3   |           data...
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	//             ...data             |
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	rawPkt := []byte{
+		0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64,
+		0x27, 0x82, 0xBE, 0xDE, 0x00, 0x03, 0x10, 0xAA, 0x21, 0xBB,
+		0xBB, 0x33, 0xCC, 0xCC, 0xCC, 0xCC,
+		// Payload
+		0x98, 0x36, 0xbe, 0x88, 0x9e,
+	}
+
+	p := &Packet{Header: Header{
+		Marker:           true,
+		Extension:        true,
+		ExtensionProfile: 0xBEDE,
+		Extensions: []Extension{
+			{1, []byte{
+				0xAA,
+			}},
+			{2, []byte{
+				0xBB, 0xBB,
+			}},
+			{3, []byte{
+				0xCC, 0xCC, 0xCC, 0xCC,
+			}},
+		},
+		Version:        2,
+		PayloadOffset:  26,
+		PayloadType:    96,
+		SequenceNumber: 27023,
+		Timestamp:      3653407706,
+		SSRC:           476325762,
+		CSRC:           []uint32{},
+	},
+		Payload: rawPkt[26:],
+		Raw:     rawPkt,
+	}
+
+	dstData, _ := p.Marshal()
+	if !bytes.Equal(dstData, rawPkt) {
+		t.Errorf("Marshal failed raw \nMarshaled: %+v,\nrawPkt:     %+v", dstData, rawPkt)
+	}
+}
+
+func TestRFC8285TwoByteExtension(t *testing.T) {
+	p := &Packet{}
+
+	rawPkt := []byte{
+		0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64,
+		0x27, 0x82, 0x10, 0x00, 0x00, 0x01, 0x05, 0x18, 0xAA, 0xAA,
+		0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+		0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+		0xAA, 0xAA, 0x98, 0x36, 0xbe, 0x88, 0x9e,
+	}
+	if err := p.Unmarshal(rawPkt); err != nil {
+		t.Fatal("Unmarshal err for valid extension")
+	}
+
+	p = &Packet{Header: Header{
+		Marker:           true,
+		Extension:        true,
+		ExtensionProfile: 0x1000,
+		Extensions: []Extension{
+			{5, []byte{
+				0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+				0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+				0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+			}},
+		},
+		Version:        2,
+		PayloadOffset:  42,
+		PayloadType:    96,
+		SequenceNumber: 27023,
+		Timestamp:      3653407706,
+		SSRC:           476325762,
+		CSRC:           []uint32{},
+	},
+		Payload: rawPkt[42:],
+		Raw:     rawPkt,
+	}
+
+	dstData, _ := p.Marshal()
+	if !bytes.Equal(dstData, rawPkt) {
+		t.Errorf("Marshal failed \nExpected %v+ \nActual:  %v+", rawPkt, dstData)
+	}
+}
+
+func TestRFC8285TwoByteMultipleExtensionsWithPadding(t *testing.T) {
+	p := &Packet{}
+
+	// 0                   1                   2                   3
+	// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	// |       0x10    |    0x00       |           length=3            |
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	// |      ID=1     |     L=0       |     ID=2      |     L=1       |
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	// |       data    |    0 (pad)    |       ID=3    |      L=4      |
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	// |                          data                                 |
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	rawPkt := []byte{
+		0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64,
+		0x27, 0x82, 0x10, 0x00, 0x00, 0x03, 0x01, 0x00, 0x02, 0x01,
+		0xBB, 0x00, 0x03, 0x04, 0xCC, 0xCC, 0xCC, 0xCC, 0x98, 0x36,
+		0xbe, 0x88, 0x9e,
+	}
+	if err := p.Unmarshal(rawPkt); err != nil {
+		t.Fatal("Unmarshal err for valid extension")
+	}
+
+	ext1 := p.GetExtension(1)
+	ext1Expect := []byte{}
+	if !bytes.Equal(ext1, ext1Expect) {
+		t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext1, ext1Expect)
+	}
+
+	ext2 := p.GetExtension(2)
+	ext2Expect := []byte{0xBB}
+	if !bytes.Equal(ext2, ext2Expect) {
+		t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext2, ext2Expect)
+	}
+
+	ext3 := p.GetExtension(3)
+	ext3Expect := []byte{0xCC, 0xCC, 0xCC, 0xCC}
+	if !bytes.Equal(ext3, ext3Expect) {
+		t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext3, ext3Expect)
+	}
+}
+
+func TestRFC8285TwoByteMultipleExtensionsWithLargeExtension(t *testing.T) {
+	// 0                   1                   2                   3
+	// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	// |       0x10    |    0x00       |           length=3            |
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	// |      ID=1     |     L=0       |     ID=2      |     L=1       |
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	// |       data    |       ID=3    |      L=17      |    data...
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	//                            ...data...
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	//                            ...data...
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	//                            ...data...
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	//                            ...data...                           |
+	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+	rawPkt := []byte{
+		0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64,
+		0x27, 0x82, 0x10, 0x00, 0x00, 0x03, 0x01, 0x00, 0x02, 0x01,
+		0xBB, 0x03, 0x11, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
+		0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
+		// Payload
+		0x98, 0x36, 0xbe, 0x88, 0x9e,
+	}
+
+	p := &Packet{Header: Header{
+		Marker:           true,
+		Extension:        true,
+		ExtensionProfile: 0x1000,
+		Extensions: []Extension{
+			{1, []byte{}},
+			{2, []byte{
+				0xBB,
+			}},
+			{3, []byte{
+				0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
+				0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
+			}},
+		},
+		Version:        2,
+		PayloadOffset:  40,
+		PayloadType:    96,
+		SequenceNumber: 27023,
+		Timestamp:      3653407706,
+		SSRC:           476325762,
+		CSRC:           []uint32{},
+	},
+		Payload: rawPkt[40:],
+		Raw:     rawPkt,
+	}
+
+	dstData, _ := p.Marshal()
+	if !bytes.Equal(dstData, rawPkt) {
+		t.Errorf("Marshal failed raw \nMarshaled: %+v,\nrawPkt:    %+v", dstData, rawPkt)
+	}
+}
+
+func TestRFC8285OneByteSetExtensionShouldEnableExensionsWhenAdding(t *testing.T) {
+	payload := []byte{
+		// Payload
+		0x98, 0x36, 0xbe, 0x88, 0x9e,
+	}
+	p := &Packet{Header: Header{
+		Marker:         true,
+		Extension:      false,
+		Version:        2,
+		PayloadOffset:  26,
+		PayloadType:    96,
+		SequenceNumber: 27023,
+		Timestamp:      3653407706,
+		SSRC:           476325762,
+		CSRC:           []uint32{},
+	},
+		Payload: payload,
+	}
+
+	extension := []byte{0xAA, 0xAA}
+	err := p.SetExtension(1, extension)
+	if err != nil {
+		t.Error("Error setting extension")
+	}
+
+	if p.Extension != true {
+		t.Error("Extension should be set to true")
+	}
+
+	if p.ExtensionProfile != 0xBEDE {
+		t.Error("Extension profile should be set to 0xBEDE")
+	}
+
+	if len(p.Extensions) != 1 {
+		t.Error("Extensions should be set to 1")
+	}
+
+	if !bytes.Equal(p.GetExtension(1), extension) {
+		t.Error("Extension value is not set")
+	}
+}
+
+func TestRFC8285OneByteSetExtensionShouldUpdateExistingExension(t *testing.T) {
+	payload := []byte{
+		// Payload
+		0x98, 0x36, 0xbe, 0x88, 0x9e,
+	}
+	p := &Packet{Header: Header{
+		Marker:           true,
+		Extension:        true,
+		ExtensionProfile: 0xBEDE,
+		Extensions: []Extension{
+			{1, []byte{
+				0xAA,
+			}},
+		},
+		Version:        2,
+		PayloadOffset:  26,
+		PayloadType:    96,
+		SequenceNumber: 27023,
+		Timestamp:      3653407706,
+		SSRC:           476325762,
+		CSRC:           []uint32{},
+	},
+		Payload: payload,
+	}
+
+	if !bytes.Equal(p.GetExtension(1), []byte{0xAA}) {
+		t.Error("Extension value not initialize properly")
+	}
+
+	extension := []byte{0xBB}
+	err := p.SetExtension(1, extension)
+	if err != nil {
+		t.Error("Error setting extension")
+	}
+
+	if !bytes.Equal(p.GetExtension(1), extension) {
+		t.Error("Extension value was not set")
+	}
+}
+
+func TestRFC8285OneByteSetExtensionShouldErrorWhenInvalidIDProvided(t *testing.T) {
+	payload := []byte{
+		// Payload
+		0x98, 0x36, 0xbe, 0x88, 0x9e,
+	}
+	p := &Packet{Header: Header{
+		Marker:           true,
+		Extension:        true,
+		ExtensionProfile: 0xBEDE,
+		Extensions: []Extension{
+			{1, []byte{
+				0xAA,
+			}},
+		},
+		Version:        2,
+		PayloadOffset:  26,
+		PayloadType:    96,
+		SequenceNumber: 27023,
+		Timestamp:      3653407706,
+		SSRC:           476325762,
+		CSRC:           []uint32{},
+	},
+		Payload: payload,
+	}
+
+	if p.SetExtension(0, []byte{0xBB}) == nil {
+		t.Error("SetExtension did not error on invalid id")
+	}
+
+	if p.SetExtension(15, []byte{0xBB}) == nil {
+		t.Error("SetExtension did not error on invalid id")
+	}
+}
+
+func TestRFC8285OneByteExtensionTermianteProcessingWhenReservedIDEncountered(t *testing.T) {
+	p := &Packet{}
+
+	reservedIDPkt := []byte{
+		0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64,
+		0x27, 0x82, 0xBE, 0xDE, 0x00, 0x01, 0xF0, 0xAA, 0x98, 0x36, 0xbe, 0x88, 0x9e,
+	}
+	if err := p.Unmarshal(reservedIDPkt); err != nil {
+		t.Error("Unmarshal error on packet with reserved extension id")
+	}
+
+	if len(p.Extensions) != 0 {
+		t.Error("Extensions should be empty for invalid id")
+	}
+
+	payload := reservedIDPkt[17:]
+	if !bytes.Equal(p.Payload, payload) {
+		t.Errorf("p.Payload must be same as payload.\n  p.Payload: %+v,\n payload: %+v",
+			p.Payload, payload,
+		)
+	}
+}
+
+func TestRFC8285OneByteSetExtensionShouldErrorWhenPayloadTooLarge(t *testing.T) {
+	payload := []byte{
+		// Payload
+		0x98, 0x36, 0xbe, 0x88, 0x9e,
+	}
+	p := &Packet{Header: Header{
+		Marker:           true,
+		Extension:        true,
+		ExtensionProfile: 0xBEDE,
+		Extensions: []Extension{
+			{1, []byte{
+				0xAA,
+			}},
+		},
+		Version:        2,
+		PayloadOffset:  26,
+		PayloadType:    96,
+		SequenceNumber: 27023,
+		Timestamp:      3653407706,
+		SSRC:           476325762,
+		CSRC:           []uint32{},
+	},
+		Payload: payload,
+	}
+
+	if p.SetExtension(1, []byte{
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+	}) == nil {
+		t.Error("SetExtension did not error on too large payload")
+	}
+}
+
+func TestRFC8285TwoByteSetExtensionShouldEnableExensionsWhenAdding(t *testing.T) {
+	payload := []byte{
+		// Payload
+		0x98, 0x36, 0xbe, 0x88, 0x9e,
+	}
+	p := &Packet{Header: Header{
+		Marker:         true,
+		Extension:      false,
+		Version:        2,
+		PayloadOffset:  31,
+		PayloadType:    96,
+		SequenceNumber: 27023,
+		Timestamp:      3653407706,
+		SSRC:           476325762,
+		CSRC:           []uint32{},
+	},
+		Payload: payload,
+	}
+
+	extension := []byte{
+		0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+		0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+	}
+	err := p.SetExtension(1, extension)
+	if err != nil {
+		t.Error("Error setting extension")
+	}
+
+	if p.Extension != true {
+		t.Error("Extension should be set to true")
+	}
+
+	if p.ExtensionProfile != 0x1000 {
+		t.Error("Extension profile should be set to 0xBEDE")
+	}
+
+	if len(p.Extensions) != 1 {
+		t.Error("Extensions should be set to 1")
+	}
+
+	if !bytes.Equal(p.GetExtension(1), extension) {
+		t.Error("Extension value is not set")
+	}
+}
+
+func TestRFC8285TwoByteSetExtensionShouldUpdateExistingExension(t *testing.T) {
+	payload := []byte{
+		// Payload
+		0x98, 0x36, 0xbe, 0x88, 0x9e,
+	}
+	p := &Packet{Header: Header{
+		Marker:           true,
+		Extension:        true,
+		ExtensionProfile: 0x1000,
+		Extensions: []Extension{
+			{1, []byte{
+				0xAA,
+			}},
+		},
+		Version:        2,
+		PayloadOffset:  26,
+		PayloadType:    96,
+		SequenceNumber: 27023,
+		Timestamp:      3653407706,
+		SSRC:           476325762,
+		CSRC:           []uint32{},
+	},
+		Payload: payload,
+	}
+
+	if !bytes.Equal(p.GetExtension(1), []byte{0xAA}) {
+		t.Error("Extension value not initialize properly")
+	}
+
+	extension := []byte{
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+	}
+	err := p.SetExtension(1, extension)
+	if err != nil {
+		t.Error("Error setting extension")
+	}
+
+	if !bytes.Equal(p.GetExtension(1), extension) {
+		t.Error("Extension value was not set")
+	}
+}
+
+func TestRFC8285TwoByteSetExtensionShouldErrorWhenPayloadTooLarge(t *testing.T) {
+	payload := []byte{
+		// Payload
+		0x98, 0x36, 0xbe, 0x88, 0x9e,
+	}
+	p := &Packet{Header: Header{
+		Marker:           true,
+		Extension:        true,
+		ExtensionProfile: 0xBEDE,
+		Extensions: []Extension{
+			{1, []byte{
+				0xAA,
+			}},
+		},
+		Version:        2,
+		PayloadOffset:  26,
+		PayloadType:    96,
+		SequenceNumber: 27023,
+		Timestamp:      3653407706,
+		SSRC:           476325762,
+		CSRC:           []uint32{},
+	},
+		Payload: payload,
+	}
+
+	if p.SetExtension(1, []byte{
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+		0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+	}) == nil {
+		t.Error("SetExtension did not error on too large payload")
+	}
+}
+
+func TestRFC3550SetExtensionShouldErrorWhenNonZero(t *testing.T) {
+	payload := []byte{
+		// Payload
+		0x98, 0x36, 0xbe, 0x88, 0x9e,
+	}
+	p := &Packet{Header: Header{
+		Marker:           true,
+		Extension:        true,
+		ExtensionProfile: 0x1111,
+		Extensions: []Extension{
+			{0, []byte{
+				0xAA,
+			}},
+		},
+		Version:        2,
+		PayloadOffset:  26,
+		PayloadType:    96,
+		SequenceNumber: 27023,
+		Timestamp:      3653407706,
+		SSRC:           476325762,
+		CSRC:           []uint32{},
+	},
+		Payload: payload,
+	}
+
+	expect := []byte{0xBB}
+	if p.SetExtension(0, expect) != nil {
+		t.Error("SetExtension should not error on valid id")
+	}
+
+	actual := p.GetExtension(0)
+	if !bytes.Equal(actual, expect) {
+		t.Error("p.GetExtension returned incorrect value.")
+	}
+}
+
+func TestRFC3550SetExtensionShouldRaiseErrorWhenSettingNonzeroID(t *testing.T) {
+	payload := []byte{
+		// Payload
+		0x98, 0x36, 0xbe, 0x88, 0x9e,
+	}
+	p := &Packet{Header: Header{
+		Marker:           true,
+		Extension:        true,
+		ExtensionProfile: 0x1111,
+		Version:          2,
+		PayloadOffset:    26,
+		PayloadType:      96,
+		SequenceNumber:   27023,
+		Timestamp:        3653407706,
+		SSRC:             476325762,
+		CSRC:             []uint32{},
+	},
+		Payload: payload,
+	}
+
+	if p.SetExtension(1, []byte{0xBB}) == nil {
+		t.Error("SetExtension did not error on invalid id")
+	}
+}
 func TestRoundtrip(t *testing.T) {
 	rawPkt := []byte{
 		0x00, 0x10, 0x23, 0x45, 0x12, 0x34, 0x45, 0x67, 0xCC, 0xDD, 0xEE, 0xFF,