Repository: pion/interceptor
Pull request #82 (2021-12-09 18:12:35)

Fix duplicate packets leading to wrap around
#### Description

Duplicate packets can lead to TWCC reports spanning a whole cycle of sequence numbers. This PR fixes the insertion into the internal log to prevent duplicate or out-of-order packets in the internal log.


Patch:
diff --git a/pkg/twcc/twcc.go b/pkg/twcc/twcc.go
index d5befbf..e0f7f0a 100644
--- a/pkg/twcc/twcc.go
+++ b/pkg/twcc/twcc.go
@@ -42,13 +42,33 @@ func (r *Recorder) Record(mediaSSRC uint32, sequenceNumber uint16, arrivalTime i
 	if sequenceNumber < 0x0fff && (r.lastSequenceNumber&0xffff) > 0xf000 {
 		r.cycles += 1 << 16
 	}
-	r.receivedPackets = append(r.receivedPackets, pktInfo{
+	r.receivedPackets = insertSorted(r.receivedPackets, pktInfo{
 		sequenceNumber: r.cycles | uint32(sequenceNumber),
 		arrivalTime:    arrivalTime,
 	})
 	r.lastSequenceNumber = sequenceNumber
 }
 
+func insertSorted(list []pktInfo, element pktInfo) []pktInfo {
+	if len(list) == 0 {
+		return append(list, element)
+	}
+	for i := len(list) - 1; i >= 0; i-- {
+		if list[i].sequenceNumber < element.sequenceNumber {
+			list = append(list, pktInfo{})
+			copy(list[i+2:], list[i+1:])
+			list[i+1] = element
+			return list
+		}
+		if list[i].sequenceNumber == element.sequenceNumber {
+			list[i] = element
+			return list
+		}
+	}
+	// element.sequenceNumber is between 0 and first ever received sequenceNumber
+	return append([]pktInfo{element}, list...)
+}
+
 // BuildFeedbackPacket creates a new RTCP packet containing a TWCC feedback report.
 func (r *Recorder) BuildFeedbackPacket() []rtcp.Packet {
 	feedback := newFeedback(r.senderSSRC, r.mediaSSRC, r.fbPktCnt)


Test patch:
diff --git a/pkg/twcc/twcc_test.go b/pkg/twcc/twcc_test.go
index 91c0dba..975c9ca 100644
--- a/pkg/twcc/twcc_test.go
+++ b/pkg/twcc/twcc_test.go
@@ -1,6 +1,7 @@
 package twcc
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/pion/rtcp"
@@ -502,3 +503,186 @@ func TestBuildFeedbackPacketCount(t *testing.T) {
 	twcc = pkts[0].(*rtcp.TransportLayerCC)
 	assert.Equal(t, uint8(1), twcc.FbPktCount)
 }
+
+func TestDuplicatePackets(t *testing.T) {
+	r := NewRecorder(5000)
+
+	arrivalTime := int64(scaleFactorReferenceTime)
+	addRun(t, r, []uint16{12, 13, 13, 14}, []int64{
+		arrivalTime,
+		arrivalTime,
+		arrivalTime,
+		arrivalTime,
+	})
+
+	rtcpPackets := r.BuildFeedbackPacket()
+	assert.Equal(t, 1, len(rtcpPackets))
+
+	assert.Equal(t, &rtcp.TransportLayerCC{
+		Header: rtcp.Header{
+			Count:   rtcp.FormatTCC,
+			Type:    rtcp.TypeTransportSpecificFeedback,
+			Padding: true,
+			Length:  6,
+		},
+		SenderSSRC:         5000,
+		MediaSSRC:          5000,
+		BaseSequenceNumber: 12,
+		ReferenceTime:      1,
+		FbPktCount:         0,
+		PacketStatusCount:  3,
+		PacketChunks: []rtcp.PacketStatusChunk{
+			&rtcp.RunLengthChunk{
+				PacketStatusChunk:  nil,
+				Type:               rtcp.TypeTCCRunLengthChunk,
+				PacketStatusSymbol: rtcp.TypeTCCPacketReceivedSmallDelta,
+				RunLength:          3,
+			},
+		},
+		RecvDeltas: []*rtcp.RecvDelta{
+			{Type: rtcp.TypeTCCPacketReceivedSmallDelta, Delta: 0},
+			{Type: rtcp.TypeTCCPacketReceivedSmallDelta, Delta: 0},
+			{Type: rtcp.TypeTCCPacketReceivedSmallDelta, Delta: 0},
+		},
+	}, rtcpPackets[0].(*rtcp.TransportLayerCC))
+	marshalAll(t, rtcpPackets)
+}
+
+func TestInsertSorted(t *testing.T) {
+	cases := []struct {
+		l        []pktInfo
+		e        pktInfo
+		expected []pktInfo
+	}{
+		{
+			l: []pktInfo{},
+			e: pktInfo{},
+			expected: []pktInfo{{
+				sequenceNumber: 0,
+				arrivalTime:    0,
+			}},
+		},
+		{
+			l: []pktInfo{
+				{
+					sequenceNumber: 0,
+					arrivalTime:    0,
+				},
+				{
+					sequenceNumber: 1,
+					arrivalTime:    0,
+				},
+			},
+			e: pktInfo{
+				sequenceNumber: 2,
+				arrivalTime:    0,
+			},
+			expected: []pktInfo{
+				{
+					sequenceNumber: 0,
+					arrivalTime:    0,
+				},
+				{
+					sequenceNumber: 1,
+					arrivalTime:    0,
+				},
+				{
+					sequenceNumber: 2,
+					arrivalTime:    0,
+				},
+			},
+		},
+		{
+			l: []pktInfo{
+				{
+					sequenceNumber: 0,
+					arrivalTime:    0,
+				},
+				{
+					sequenceNumber: 2,
+					arrivalTime:    0,
+				},
+			},
+			e: pktInfo{
+				sequenceNumber: 1,
+				arrivalTime:    0,
+			},
+			expected: []pktInfo{
+				{
+					sequenceNumber: 0,
+					arrivalTime:    0,
+				},
+				{
+					sequenceNumber: 1,
+					arrivalTime:    0,
+				},
+				{
+					sequenceNumber: 2,
+					arrivalTime:    0,
+				},
+			},
+		},
+		{
+			l: []pktInfo{
+				{
+					sequenceNumber: 0,
+					arrivalTime:    0,
+				},
+				{
+					sequenceNumber: 1,
+					arrivalTime:    0,
+				},
+				{
+					sequenceNumber: 2,
+					arrivalTime:    0,
+				},
+			},
+			e: pktInfo{
+				sequenceNumber: 1,
+				arrivalTime:    0,
+			},
+			expected: []pktInfo{
+				{
+					sequenceNumber: 0,
+					arrivalTime:    0,
+				},
+				{
+					sequenceNumber: 1,
+					arrivalTime:    0,
+				},
+				{
+					sequenceNumber: 2,
+					arrivalTime:    0,
+				},
+			},
+		},
+		{
+			l: []pktInfo{
+				{
+					sequenceNumber: 10,
+					arrivalTime:    0,
+				},
+			},
+			e: pktInfo{
+				sequenceNumber: 9,
+				arrivalTime:    0,
+			},
+			expected: []pktInfo{
+				{
+					sequenceNumber: 9,
+					arrivalTime:    0,
+				},
+				{
+					sequenceNumber: 10,
+					arrivalTime:    0,
+				},
+			},
+		},
+	}
+	for i, c := range cases {
+		c := c
+		t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
+			assert.Equal(t, c.expected, insertSorted(c.l, c.e))
+		})
+	}
+}