Skip to content

Commit 5a23b30

Browse files
committed
Fix malformed FlexFEC-03
1 parent f30b304 commit 5a23b30

8 files changed

+890
-28
lines changed

pkg/flexfec/flexfec_03_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
package flexfec
5+
6+
import (
7+
"testing"
8+
9+
"github.com/pion/rtp"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
const (
15+
payloadType = uint8(49)
16+
ssrc = uint32(867589674)
17+
protectedStreamSSRC = uint32(476325762)
18+
)
19+
20+
func checkAnyPacketCanBeRecovered(t *testing.T, mediaPackets []rtp.Packet, fecPackets []rtp.Packet) {
21+
t.Helper()
22+
23+
for lost := 0; lost < len(mediaPackets); lost++ {
24+
decoder := newFECDecoder(ssrc, protectedStreamSSRC)
25+
recoveredPackets := make([]rtp.Packet, 0)
26+
// lose one packet
27+
for _, mediaPacket := range mediaPackets[:lost] {
28+
recoveredPackets = append(recoveredPackets, decoder.DecodeFec(mediaPacket)...)
29+
}
30+
for _, mediaPacket := range mediaPackets[lost+1:] {
31+
recoveredPackets = append(recoveredPackets, decoder.DecodeFec(mediaPacket)...)
32+
}
33+
assert.Empty(t, recoveredPackets)
34+
35+
for _, fecPacket := range fecPackets {
36+
recoveredPackets = append(recoveredPackets, decoder.DecodeFec(fecPacket)...)
37+
}
38+
39+
require.Len(t, recoveredPackets, 1)
40+
assert.Equal(t, mediaPackets[lost], recoveredPackets[0])
41+
}
42+
}
43+
44+
func generatePackets(t *testing.T, seqs []uint16) ([]rtp.Packet, []rtp.Packet) {
45+
t.Helper()
46+
47+
mediaPackets := make([]rtp.Packet, 0)
48+
for i, seq := range seqs {
49+
payload := []byte{
50+
// Payload
51+
1, 2, 3, 4, 5, byte(i),
52+
}
53+
packet := rtp.Packet{
54+
Header: rtp.Header{
55+
Marker: true,
56+
Extension: false,
57+
Version: 2,
58+
PayloadType: 96,
59+
60+
SequenceNumber: seq,
61+
Timestamp: 3653407706,
62+
SSRC: protectedStreamSSRC,
63+
CSRC: []uint32{},
64+
},
65+
Payload: payload,
66+
}
67+
68+
extension := []byte{0xAA, 0xAA}
69+
err := packet.SetExtension(1, extension)
70+
require.NoError(t, err)
71+
72+
mediaPackets = append(mediaPackets, packet)
73+
}
74+
75+
encoder := NewFlexEncoder03(payloadType, ssrc)
76+
fecPackets := encoder.EncodeFec(mediaPackets, 2)
77+
78+
return mediaPackets, fecPackets
79+
}
80+
81+
func TestFlexFec03_SimpleRoundTrip(t *testing.T) {
82+
tests := []struct {
83+
name string
84+
seqs []uint16
85+
}{
86+
{
87+
name: "first",
88+
seqs: []uint16{1, 2, 3, 4, 5},
89+
},
90+
{
91+
name: "last",
92+
seqs: []uint16{65531, 65532, 65533, 65534, 65535},
93+
},
94+
{
95+
name: "wrap",
96+
seqs: []uint16{65533, 65534, 65535, 0, 1},
97+
},
98+
}
99+
100+
for _, test := range tests {
101+
t.Run(test.name, func(t *testing.T) {
102+
mediaPackets, fecPackets := generatePackets(t, test.seqs)
103+
require.Len(t, mediaPackets, len(test.seqs))
104+
checkAnyPacketCanBeRecovered(t, mediaPackets, fecPackets)
105+
})
106+
}
107+
}
108+
109+
func TestFlexFec03_WholeRangeRoundTrip(t *testing.T) {
110+
var seqs []uint16
111+
const maxFlexFEC03MediaPackets = 109
112+
for i := 0; i < maxFlexFEC03MediaPackets; i++ {
113+
seqs = append(seqs, uint16(i)) //nolint:gosec
114+
}
115+
116+
mediaPackets, fecPackets := generatePackets(t, seqs)
117+
require.Len(t, mediaPackets, len(seqs))
118+
checkAnyPacketCanBeRecovered(t, mediaPackets, fecPackets)
119+
}

pkg/flexfec/flexfec_coverage.go

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -119,29 +119,44 @@ func (p *ProtectionCoverage) GetCoveredBy(fecPacketIndex uint32) *util.MediaPack
119119
// ExtractMask1 returns the first section of the bitmask as defined by the FEC header.
120120
// https://datatracker.ietf.org/doc/html/rfc8627#section-4.2.2.1
121121
func (p *ProtectionCoverage) ExtractMask1(fecPacketIndex uint32) uint16 {
122-
mask := p.packetMasks[fecPacketIndex]
122+
return extractMask1(p.packetMasks[fecPacketIndex])
123+
}
124+
125+
// ExtractMask2 returns the second section of the bitmask as defined by the FEC header.
126+
// https://datatracker.ietf.org/doc/html/rfc8627#section-4.2.2.1
127+
func (p *ProtectionCoverage) ExtractMask2(fecPacketIndex uint32) uint32 {
128+
return extractMask2(p.packetMasks[fecPacketIndex])
129+
}
130+
131+
// ExtractMask3 returns the third section of the bitmask as defined by the FEC header.
132+
// https://datatracker.ietf.org/doc/html/rfc8627#section-4.2.2.1
133+
func (p *ProtectionCoverage) ExtractMask3(fecPacketIndex uint32) uint64 {
134+
return extractMask3(p.packetMasks[fecPacketIndex])
135+
}
136+
137+
// ExtractMask3_03 returns the third section of the bitmask as defined by the FEC header.
138+
// https://datatracker.ietf.org/doc/html/draft-ietf-payload-flexible-fec-scheme-03#section-4.2
139+
func (p *ProtectionCoverage) ExtractMask3_03(fecPacketIndex uint32) uint64 {
140+
return extractMask3_03(p.packetMasks[fecPacketIndex])
141+
}
142+
143+
func extractMask1(mask util.BitArray) uint16 {
123144
// We get the first 16 bits (64 - 16 -> shift by 48) and we shift once more for K field
124145
mask1 := mask.Lo >> 49
125146

126147
return uint16(mask1) //nolint:gosec // G115
127148
}
128149

129-
// ExtractMask2 returns the second section of the bitmask as defined by the FEC header.
130-
// https://datatracker.ietf.org/doc/html/rfc8627#section-4.2.2.1
131-
func (p *ProtectionCoverage) ExtractMask2(fecPacketIndex uint32) uint32 {
132-
mask := p.packetMasks[fecPacketIndex]
150+
func extractMask2(mask util.BitArray) uint32 {
133151
// We remove the first 15 bits
134152
mask2 := mask.Lo << 15
135153
// We get the first 31 bits (64 - 31 -> shift by 33) and we shift once more for K field
136-
mask2 >>= 34
154+
mask2 >>= 33
137155

138-
return uint32(mask2) //nolint:gosec // G115
156+
return uint32(mask2) //nolint:gosec
139157
}
140158

141-
// ExtractMask3 returns the third section of the bitmask as defined by the FEC header.
142-
// https://datatracker.ietf.org/doc/html/rfc8627#section-4.2.2.1
143-
func (p *ProtectionCoverage) ExtractMask3(fecPacketIndex uint32) uint64 {
144-
mask := p.packetMasks[fecPacketIndex]
159+
func extractMask3(mask util.BitArray) uint64 {
145160
// We remove the first 46 bits
146161
maskLo := mask.Lo << 46
147162
maskHi := mask.Hi >> 18
@@ -150,10 +165,7 @@ func (p *ProtectionCoverage) ExtractMask3(fecPacketIndex uint32) uint64 {
150165
return mask3
151166
}
152167

153-
// ExtractMask3_03 returns the third section of the bitmask as defined by the FEC header.
154-
// https://datatracker.ietf.org/doc/html/draft-ietf-payload-flexible-fec-scheme-03#section-4.2
155-
func (p *ProtectionCoverage) ExtractMask3_03(fecPacketIndex uint32) uint64 {
156-
mask := p.packetMasks[fecPacketIndex]
168+
func extractMask3_03(mask util.BitArray) uint64 {
157169
// We remove the first 46 bits
158170
maskLo := mask.Lo << 46
159171
maskHi := mask.Hi >> 18

pkg/flexfec/flexfec_coverage_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
package flexfec
5+
6+
import (
7+
"testing"
8+
9+
"github.com/pion/interceptor/pkg/flexfec/util"
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestMaskExtractors(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
setBits []uint32
17+
mask1 uint16
18+
mask2 uint32
19+
mask3 uint64
20+
mask3_03 uint64
21+
}{
22+
{
23+
name: "Empty mask",
24+
setBits: []uint32{},
25+
mask1: 0,
26+
mask2: 0,
27+
mask3: 0,
28+
mask3_03: 0,
29+
},
30+
{
31+
name: "Single bit in each mask",
32+
setBits: []uint32{5, 20, 50},
33+
mask1: 0x200, // bit 5
34+
mask2: 0x2000000, // bit 20
35+
mask3: 0x800000000000000, // bit 50
36+
mask3_03: 0x400000000000000, // bit 50
37+
},
38+
{
39+
name: "Multiple bits in each mask",
40+
setBits: []uint32{0, 7, 14, 15, 30, 45, 46, 80, 108, 109},
41+
mask1: 0x4081, // bits 0, 7, 14
42+
mask2: 0x40008001, // bits 15, 30, 45
43+
mask3: 0x8000000020000003, // bits 46, 80, 108, 109
44+
mask3_03: 0x4000000010000001, // bits 46, 80, 108
45+
},
46+
{
47+
name: "Boundary values",
48+
setBits: []uint32{0, 14, 15, 45, 46, 108, 109},
49+
mask1: 0x4001, // bits 0, 14
50+
mask2: 0x40000001, // bits 15, 45
51+
mask3: 0x8000000000000003, // bits 46, 108, 109
52+
mask3_03: 0x4000000000000001, // bits 46, 108
53+
},
54+
}
55+
56+
for _, tt := range tests {
57+
t.Run(tt.name, func(t *testing.T) {
58+
mask := util.BitArray{}
59+
60+
for _, bit := range tt.setBits {
61+
mask.SetBit(bit)
62+
}
63+
64+
actualMask1 := extractMask1(mask)
65+
actualMask2 := extractMask2(mask)
66+
actualMask3 := extractMask3(mask)
67+
actualMask3_03 := extractMask3_03(mask)
68+
69+
assert.Equal(t, tt.mask1, actualMask1, "Mask1 mismatch")
70+
assert.Equal(t, tt.mask2, actualMask2, "Mask2 mismatch")
71+
assert.Equal(t, tt.mask3, actualMask3, "Mask3 mismatch")
72+
assert.Equal(t, tt.mask3_03, actualMask3_03, "Mask3_03 mismatch")
73+
})
74+
}
75+
}

0 commit comments

Comments
 (0)