Skip to content

Commit 758a4e9

Browse files
committed
Implement interleaving 8620
1 parent 767b9c4 commit 758a4e9

13 files changed

+1079
-65
lines changed

association.go

Lines changed: 233 additions & 27 deletions
Large diffs are not rendered by default.

association_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2643,7 +2643,7 @@ func TestAssocHandleInit(t *testing.T) {
26432643
init.numInboundStreams = 1002
26442644
init.initiateTag = 5678
26452645
init.advertisedReceiverWindowCredit = 512 * 1024
2646-
setSupportedExtensions(&init.chunkInitCommon)
2646+
setSupportedExtensions(&init.chunkInitCommon, false)
26472647

26482648
_, err := assoc.handleInit(pkt, init)
26492649
if expectErr {

chunk_i_data_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
package sctp
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestChunkIDataMarshalUnmarshal(t *testing.T) {
13+
t.Run("beginning fragment", func(t *testing.T) {
14+
orig := &chunkPayloadData{
15+
iData: true,
16+
tsn: 1,
17+
streamIdentifier: 2,
18+
messageIdentifier: 5,
19+
fragmentSequenceNumber: 0,
20+
payloadType: PayloadTypeWebRTCBinary,
21+
userData: []byte("abc"),
22+
beginningFragment: true,
23+
endingFragment: true,
24+
}
25+
26+
raw, err := orig.marshal()
27+
assert.NoError(t, err)
28+
29+
parsed := &chunkPayloadData{}
30+
err = parsed.unmarshal(raw)
31+
assert.NoError(t, err)
32+
assert.True(t, parsed.isIData())
33+
assert.Equal(t, orig.tsn, parsed.tsn)
34+
assert.Equal(t, orig.streamIdentifier, parsed.streamIdentifier)
35+
assert.Equal(t, orig.messageIdentifier, parsed.messageIdentifier)
36+
assert.Equal(t, orig.fragmentSequenceNumber, parsed.fragmentSequenceNumber)
37+
assert.Equal(t, orig.payloadType, parsed.payloadType)
38+
assert.Equal(t, orig.userData, parsed.userData)
39+
assert.True(t, parsed.beginningFragment)
40+
assert.True(t, parsed.endingFragment)
41+
})
42+
43+
t.Run("middle fragment", func(t *testing.T) {
44+
orig := &chunkPayloadData{
45+
iData: true,
46+
tsn: 2,
47+
streamIdentifier: 3,
48+
messageIdentifier: 7,
49+
fragmentSequenceNumber: 4,
50+
payloadType: PayloadTypeWebRTCBinary,
51+
userData: []byte("def"),
52+
beginningFragment: false,
53+
endingFragment: false,
54+
}
55+
56+
raw, err := orig.marshal()
57+
assert.NoError(t, err)
58+
59+
parsed := &chunkPayloadData{}
60+
err = parsed.unmarshal(raw)
61+
assert.NoError(t, err)
62+
assert.True(t, parsed.isIData())
63+
assert.Equal(t, orig.tsn, parsed.tsn)
64+
assert.Equal(t, orig.streamIdentifier, parsed.streamIdentifier)
65+
assert.Equal(t, orig.messageIdentifier, parsed.messageIdentifier)
66+
assert.Equal(t, orig.fragmentSequenceNumber, parsed.fragmentSequenceNumber)
67+
assert.Equal(t, PayloadTypeUnknown, parsed.payloadType)
68+
assert.Equal(t, orig.userData, parsed.userData)
69+
assert.False(t, parsed.beginningFragment)
70+
assert.False(t, parsed.endingFragment)
71+
})
72+
}

chunk_i_forward_tsn.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
package sctp
5+
6+
import (
7+
"encoding/binary"
8+
"errors"
9+
"fmt"
10+
)
11+
12+
// This chunk be used by the data sender to inform the data
13+
// receiver to adjust its cumulative received TSN.
14+
//
15+
// 0 1 2 3
16+
// 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
17+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
18+
// | Type = 194 | Flags = 0x00 | Length = Variable |
19+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20+
// | New Cumulative TSN |
21+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
22+
// | Stream Identifier | Reserved |U| |
23+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24+
// | Message Identifier |
25+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
26+
// \ /
27+
// / \
28+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
29+
// | Stream Identifier | Reserved |U| |
30+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
31+
// | Message Identifier |
32+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
33+
// https://www.rfc-editor.org/rfc/rfc8260.html#section-2.3.1
34+
35+
type chunkIForwardTSN struct {
36+
chunkHeader
37+
38+
// This indicates the new cumulative TSN to the data receiver.
39+
newCumulativeTSN uint32
40+
41+
streams []chunkIForwardTSNStream
42+
}
43+
44+
const (
45+
iForwardTSNEntryLength = 8
46+
)
47+
48+
// I-FORWARD-TSN chunk errors.
49+
var (
50+
ErrIForwardTSNChunkTooShort = errors.New("i-forward-tsn chunk too short")
51+
)
52+
53+
func (c *chunkIForwardTSN) unmarshal(raw []byte) error {
54+
if err := c.chunkHeader.unmarshal(raw); err != nil {
55+
return err
56+
}
57+
58+
if len(c.raw) < newCumulativeTSNLength {
59+
return ErrIForwardTSNChunkTooShort
60+
}
61+
62+
c.newCumulativeTSN = binary.BigEndian.Uint32(c.raw[0:])
63+
64+
offset := newCumulativeTSNLength
65+
remaining := len(c.raw) - offset
66+
for remaining > 0 {
67+
s := chunkIForwardTSNStream{}
68+
if err := s.unmarshal(c.raw[offset:]); err != nil {
69+
return fmt.Errorf("%w: %v", ErrMarshalStreamFailed, err) //nolint:errorlint
70+
}
71+
72+
c.streams = append(c.streams, s)
73+
74+
offset += s.length()
75+
remaining -= s.length()
76+
}
77+
78+
return nil
79+
}
80+
81+
func (c *chunkIForwardTSN) marshal() ([]byte, error) {
82+
out := make([]byte, newCumulativeTSNLength)
83+
binary.BigEndian.PutUint32(out[0:], c.newCumulativeTSN)
84+
85+
for _, s := range c.streams {
86+
b, err := s.marshal()
87+
if err != nil {
88+
return nil, fmt.Errorf("%w: %v", ErrMarshalStreamFailed, err) //nolint:errorlint
89+
}
90+
out = append(out, b...) //nolint:makezero // TODO: fix
91+
}
92+
93+
c.typ = ctIForwardTSN
94+
c.raw = out
95+
96+
return c.chunkHeader.marshal()
97+
}
98+
99+
func (c *chunkIForwardTSN) check() (abort bool, err error) {
100+
return true, nil
101+
}
102+
103+
// String makes chunkIForwardTSN printable.
104+
func (c *chunkIForwardTSN) String() string {
105+
res := fmt.Sprintf("New Cumulative TSN: %d\n", c.newCumulativeTSN)
106+
for _, s := range c.streams {
107+
res += fmt.Sprintf(" - si=%d mid=%d unordered=%v\n", s.identifier, s.messageIdentifier, s.unordered)
108+
}
109+
110+
return res
111+
}
112+
113+
type chunkIForwardTSNStream struct {
114+
identifier uint16
115+
unordered bool
116+
messageIdentifier uint32
117+
}
118+
119+
func (s *chunkIForwardTSNStream) length() int {
120+
return iForwardTSNEntryLength
121+
}
122+
123+
func (s *chunkIForwardTSNStream) unmarshal(raw []byte) error {
124+
if len(raw) < iForwardTSNEntryLength {
125+
return ErrIForwardTSNChunkTooShort
126+
}
127+
s.identifier = binary.BigEndian.Uint16(raw[0:])
128+
flags := binary.BigEndian.Uint16(raw[2:])
129+
s.unordered = flags&0x1 != 0
130+
s.messageIdentifier = binary.BigEndian.Uint32(raw[4:])
131+
132+
return nil
133+
}
134+
135+
func (s *chunkIForwardTSNStream) marshal() ([]byte, error) { // nolint:unparam
136+
out := make([]byte, iForwardTSNEntryLength)
137+
138+
binary.BigEndian.PutUint16(out[0:], s.identifier)
139+
if s.unordered {
140+
binary.BigEndian.PutUint16(out[2:], 0x1)
141+
} else {
142+
binary.BigEndian.PutUint16(out[2:], 0x0)
143+
}
144+
binary.BigEndian.PutUint32(out[4:], s.messageIdentifier)
145+
146+
return out, nil
147+
}

chunk_i_forward_tsn_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
package sctp
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func testChunkIForwardTSN() []byte {
13+
return []byte{0xc2, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x3}
14+
}
15+
16+
func TestChunkIForwardTSN_Success(t *testing.T) {
17+
tt := []struct {
18+
binary []byte
19+
}{
20+
{testChunkIForwardTSN()},
21+
{[]byte{0xc2, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x3, 0x0, 0x4, 0x0, 0x1, 0x0, 0x0, 0x0, 0x5}},
22+
{[]byte{
23+
0xc2, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x3,
24+
0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5,
25+
0x0, 0x6, 0x0, 0x1, 0x0, 0x0, 0x0, 0x7,
26+
0x0, 0x8, 0x0, 0x1, 0x0, 0x0, 0x0, 0x9,
27+
0x0, 0xa, 0x0, 0x1, 0x0, 0x0, 0x0, 0xb,
28+
0x0, 0xc, 0x0, 0x1, 0x0, 0x0, 0x0, 0xd,
29+
0x0, 0xe, 0x0, 0x1, 0x0, 0x0, 0x0, 0xf,
30+
0x0, 0x10, 0x0, 0x1, 0x0, 0x0, 0x0, 0x11,
31+
}},
32+
}
33+
34+
for i, tc := range tt {
35+
actual := &chunkIForwardTSN{}
36+
err := actual.unmarshal(tc.binary)
37+
assert.NoErrorf(t, err, "failed to unmarshal #%d", i)
38+
39+
b, err := actual.marshal()
40+
assert.NoError(t, err)
41+
assert.Equalf(t, tc.binary, b, "test %d not equal", i)
42+
}
43+
}
44+
45+
func TestChunkIForwardTSNUnmarshal_Failure(t *testing.T) {
46+
tt := []struct {
47+
name string
48+
binary []byte
49+
}{
50+
{"chunk header to short", []byte{0xc2}},
51+
{"missing New Cumulative TSN", []byte{0xc2, 0x0, 0x0, 0x4}},
52+
{"missing stream info", []byte{0xc2, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x3, 0x0, 0x4, 0x0, 0x1}},
53+
}
54+
55+
for i, tc := range tt {
56+
actual := &chunkIForwardTSN{}
57+
err := actual.unmarshal(tc.binary)
58+
assert.Errorf(t, err, "expected unmarshal #%d: '%s' to fail.", i, tc.name)
59+
}
60+
}

0 commit comments

Comments
 (0)