Skip to content

Commit 29ed51d

Browse files
committed
Update param_reconfig_response to RFC 6525
1 parent e83061b commit 29ed51d

File tree

2 files changed

+282
-9
lines changed

2 files changed

+282
-9
lines changed

param_reconfig_response.go

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import (
1010
)
1111

1212
// This parameter is used by the receiver of a Re-configuration Request
13-
// Parameter to respond to the request.
13+
// Parameter to respond to the request. (RFC 6525, which is referenced by RFC 9260)
1414
//
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
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
1717
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1818
// | Parameter Type = 16 | Parameter Length |
1919
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@@ -28,12 +28,20 @@ import (
2828

2929
type paramReconfigResponse struct {
3030
paramHeader
31+
3132
// This value is copied from the request parameter and is used by the
3233
// receiver of the Re-configuration Response Parameter to tie the
3334
// response to the request.
3435
reconfigResponseSequenceNumber uint32
36+
3537
// This value describes the result of the processing of the request.
3638
result reconfigResult
39+
40+
// Optional fields (RFC 6525).
41+
senderNextTSNPresent bool
42+
senderNextTSN uint32
43+
receiverNextTSNPresent bool
44+
receiverNextTSN uint32
3745
}
3846

3947
type reconfigResult uint32
@@ -50,7 +58,9 @@ const (
5058

5159
// Reconfiguration response errors.
5260
var (
53-
ErrReconfigRespParamTooShort = errors.New("reconfig response parameter too short")
61+
ErrReconfigRespParamTooShort = errors.New("reconfig response parameter too short")
62+
ErrReconfigRespParamInvalidLength = errors.New("reconfig response parameter invalid length")
63+
ErrReconfigRespParamInvalidCombo = errors.New("receiverNextTSN present requires senderNextTSN present")
5464
)
5565

5666
func (t reconfigResult) String() string {
@@ -76,23 +86,65 @@ func (t reconfigResult) String() string {
7686

7787
func (r *paramReconfigResponse) marshal() ([]byte, error) {
7888
r.typ = reconfigResp
79-
r.raw = make([]byte, 8)
80-
binary.BigEndian.PutUint32(r.raw, r.reconfigResponseSequenceNumber)
89+
90+
// Enforce ordering: receiverNextTSN can only be present if senderNextTSN is present.
91+
if r.receiverNextTSNPresent && !r.senderNextTSNPresent {
92+
return nil, ErrReconfigRespParamInvalidCombo
93+
}
94+
95+
valueLen := 8
96+
if r.senderNextTSNPresent {
97+
valueLen += 4
98+
}
99+
if r.receiverNextTSNPresent {
100+
valueLen += 4
101+
}
102+
103+
r.raw = make([]byte, valueLen)
104+
binary.BigEndian.PutUint32(r.raw[0:], r.reconfigResponseSequenceNumber)
81105
binary.BigEndian.PutUint32(r.raw[4:], uint32(r.result))
82106

107+
off := 8
108+
if r.senderNextTSNPresent {
109+
binary.BigEndian.PutUint32(r.raw[off:], r.senderNextTSN)
110+
off += 4
111+
}
112+
113+
if r.receiverNextTSNPresent {
114+
binary.BigEndian.PutUint32(r.raw[off:], r.receiverNextTSN)
115+
}
116+
83117
return r.paramHeader.marshal()
84118
}
85119

86120
func (r *paramReconfigResponse) unmarshal(raw []byte) (param, error) {
87-
err := r.paramHeader.unmarshal(raw)
88-
if err != nil {
121+
if err := r.paramHeader.unmarshal(raw); err != nil {
89122
return nil, err
90123
}
124+
91125
if len(r.raw) < 8 {
92126
return nil, ErrReconfigRespParamTooShort
93127
}
128+
129+
switch len(r.raw) {
130+
case 8, 12, 16:
131+
default:
132+
return nil, ErrReconfigRespParamInvalidLength
133+
}
134+
94135
r.reconfigResponseSequenceNumber = binary.BigEndian.Uint32(r.raw)
95136
r.result = reconfigResult(binary.BigEndian.Uint32(r.raw[4:]))
96137

138+
// Optional fields: Sender's Next TSN, Receiver's Next TSN.
139+
if len(r.raw) >= 12 {
140+
r.senderNextTSNPresent = true
141+
r.senderNextTSN = binary.BigEndian.Uint32(r.raw[8:])
142+
}
143+
144+
if len(r.raw) == 16 {
145+
r.receiverNextTSNPresent = true
146+
r.receiverNextTSN = binary.BigEndian.Uint32(r.raw[12:])
147+
}
148+
97149
return r, nil
98150
}

param_reconfig_response_test.go

Lines changed: 222 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package sctp
55

66
import (
7+
"encoding/binary"
78
"testing"
89

910
"github.com/stretchr/testify/assert"
@@ -60,7 +61,7 @@ func TestParamReconfigResponse_Failure(t *testing.T) {
6061
}
6162
}
6263

63-
func TestReconfigResultStringer(t *testing.T) {
64+
func TestReconfigResultString(t *testing.T) {
6465
tt := []struct {
6566
result reconfigResult
6667
expected string
@@ -79,3 +80,223 @@ func TestReconfigResultStringer(t *testing.T) {
7980
assert.Equalf(t, tc.expected, actual, "Test case %d", i)
8081
}
8182
}
83+
84+
func TestParamReconfigResponse_OptionalFields(t *testing.T) {
85+
type tc struct {
86+
name string
87+
raw []byte
88+
wantRSN uint32
89+
wantResult reconfigResult
90+
wantSenderPresent bool
91+
wantSender uint32
92+
wantReceiverPresent bool
93+
wantReceiver uint32
94+
wantParamHeaderLen int
95+
}
96+
97+
// build a parameter with optional fields.
98+
build := func(rsn uint32, res reconfigResult, sender *uint32, receiver *uint32) []byte {
99+
// +4 if sender and +4 if receiver)
100+
valLen := 8
101+
if sender != nil {
102+
valLen += 4
103+
}
104+
105+
if receiver != nil {
106+
valLen += 4
107+
}
108+
109+
total := 4 + valLen // header + value
110+
b := make([]byte, total)
111+
112+
// header
113+
binary.BigEndian.PutUint16(b[0:], uint16(reconfigResp))
114+
binary.BigEndian.PutUint16(b[2:], uint16(total)) //nolint:gosec
115+
116+
// value
117+
binary.BigEndian.PutUint32(b[4:], rsn)
118+
binary.BigEndian.PutUint32(b[8:], uint32(res))
119+
120+
off := 12
121+
if sender != nil {
122+
binary.BigEndian.PutUint32(b[off:], *sender)
123+
off += 4
124+
}
125+
126+
if receiver != nil {
127+
binary.BigEndian.PutUint32(b[off:], *receiver)
128+
}
129+
130+
return b
131+
}
132+
133+
senderOnly := uint32(0x01020304)
134+
bothSender := uint32(0xA1B2C3D4)
135+
bothReceiver := uint32(0x0A0B0C0D)
136+
137+
tests := []tc{
138+
{
139+
name: "sender_next_only",
140+
raw: build(0x0000002A, reconfigResultErrorWrongSSN, &senderOnly, nil),
141+
wantRSN: 0x0000002A,
142+
wantResult: reconfigResultErrorWrongSSN,
143+
wantSenderPresent: true,
144+
wantSender: senderOnly,
145+
wantReceiverPresent: false,
146+
wantParamHeaderLen: 16, // 4 (hdr) + 12 (value)
147+
},
148+
{
149+
name: "sender_and_receiver_next",
150+
raw: build(0x00000007, reconfigResultSuccessPerformed, &bothSender, &bothReceiver),
151+
wantRSN: 0x00000007,
152+
wantResult: reconfigResultSuccessPerformed,
153+
wantSenderPresent: true,
154+
wantSender: bothSender,
155+
wantReceiverPresent: true,
156+
wantReceiver: bothReceiver,
157+
wantParamHeaderLen: 20, // 4 (hdr) + 16 (value)
158+
},
159+
}
160+
161+
for _, tt := range tests {
162+
t.Run(tt.name, func(t *testing.T) {
163+
var param paramReconfigResponse
164+
165+
_, err := param.unmarshal(tt.raw)
166+
assert.NoError(t, err)
167+
168+
assert.Equal(t, reconfigResp, param.paramHeader.typ)
169+
assert.Equal(t, tt.wantParamHeaderLen, param.paramHeader.len)
170+
// p.paramHeader.raw should equal just the value part
171+
assert.Equal(t, tt.raw[4:], param.paramHeader.raw)
172+
173+
assert.Equal(t, tt.wantRSN, param.reconfigResponseSequenceNumber)
174+
assert.Equal(t, tt.wantResult, param.result)
175+
assert.Equal(t, tt.wantSenderPresent, param.senderNextTSNPresent)
176+
177+
if tt.wantSenderPresent {
178+
assert.Equal(t, tt.wantSender, param.senderNextTSN)
179+
}
180+
181+
assert.Equal(t, tt.wantReceiverPresent, param.receiverNextTSNPresent)
182+
183+
if tt.wantReceiverPresent {
184+
assert.Equal(t, tt.wantReceiver, param.receiverNextTSN)
185+
}
186+
187+
out, err := param.marshal()
188+
assert.NoError(t, err)
189+
assert.Equal(t, tt.raw, out)
190+
})
191+
}
192+
}
193+
194+
func TestParamReconfigResponse_OptionalFields_Roundtrip(t *testing.T) {
195+
tcs := []struct {
196+
name string
197+
input paramReconfigResponse
198+
valLen int // value length excluding the 4-byte header (8/12/16)
199+
}{
200+
{
201+
name: "NoOptionals",
202+
input: paramReconfigResponse{
203+
reconfigResponseSequenceNumber: 0x0A,
204+
result: reconfigResultSuccessPerformed,
205+
},
206+
valLen: 8,
207+
},
208+
{
209+
name: "SenderOnly",
210+
input: paramReconfigResponse{
211+
reconfigResponseSequenceNumber: 0x0B,
212+
result: reconfigResultDenied,
213+
senderNextTSNPresent: true,
214+
senderNextTSN: 0x12345678,
215+
},
216+
valLen: 12,
217+
},
218+
{
219+
name: "SenderAndReceiver",
220+
input: paramReconfigResponse{
221+
reconfigResponseSequenceNumber: 0x0C,
222+
result: reconfigResultInProgress,
223+
senderNextTSNPresent: true,
224+
senderNextTSN: 0x11111111,
225+
receiverNextTSNPresent: true,
226+
receiverNextTSN: 0x22222222,
227+
},
228+
valLen: 16,
229+
},
230+
}
231+
232+
for _, tc := range tcs {
233+
t.Run(tc.name, func(t *testing.T) {
234+
b, err := tc.input.marshal()
235+
assert.NoError(t, err)
236+
assert.GreaterOrEqual(t, len(b), 4)
237+
238+
pt := binary.BigEndian.Uint16(b[0:2])
239+
pl := binary.BigEndian.Uint16(b[2:4])
240+
241+
assert.Equal(t, uint16(reconfigResp), pt)
242+
assert.Equal(t, uint16(4+tc.valLen), pl) //nolint:gosec
243+
assert.Equal(t, int(pl), len(b))
244+
245+
var out paramReconfigResponse
246+
p, err := out.unmarshal(b)
247+
248+
assert.NoError(t, err)
249+
got, ok := p.(*paramReconfigResponse)
250+
assert.True(t, ok, "expected *paramReconfigResponse, got %T", p)
251+
252+
assert.Equal(t, tc.input.reconfigResponseSequenceNumber, got.reconfigResponseSequenceNumber)
253+
assert.Equal(t, tc.input.result, got.result)
254+
assert.Equal(t, tc.input.senderNextTSNPresent, got.senderNextTSNPresent)
255+
assert.Equal(t, tc.input.senderNextTSN, got.senderNextTSN)
256+
assert.Equal(t, tc.input.receiverNextTSNPresent, got.receiverNextTSNPresent)
257+
assert.Equal(t, tc.input.receiverNextTSN, got.receiverNextTSN)
258+
259+
b2, err := got.marshal()
260+
assert.NoError(t, err)
261+
assert.Equal(t, b, b2)
262+
})
263+
}
264+
}
265+
266+
func TestParamReconfigResponse_Marshal_OrderingGuard(t *testing.T) {
267+
// receiver present without sender present must fail
268+
r := paramReconfigResponse{
269+
reconfigResponseSequenceNumber: 1,
270+
result: reconfigResultSuccessPerformed,
271+
receiverNextTSNPresent: true,
272+
receiverNextTSN: 7,
273+
}
274+
275+
_, err := r.marshal()
276+
assert.Error(t, err)
277+
assert.ErrorIs(t, err, ErrReconfigRespParamInvalidCombo)
278+
}
279+
280+
func TestParamReconfigResponse_Unmarshal_InvalidLengths(t *testing.T) {
281+
// too short
282+
badShort := make([]byte, 10)
283+
binary.BigEndian.PutUint16(badShort[0:], uint16(reconfigResp))
284+
binary.BigEndian.PutUint16(badShort[2:], uint16(10))
285+
286+
var a paramReconfigResponse
287+
_, err := a.unmarshal(badShort)
288+
289+
assert.Error(t, err)
290+
assert.ErrorIs(t, err, ErrReconfigRespParamTooShort)
291+
292+
// invalid value length
293+
badLen := make([]byte, 14)
294+
binary.BigEndian.PutUint16(badLen[0:], uint16(reconfigResp))
295+
binary.BigEndian.PutUint16(badLen[2:], uint16(14))
296+
297+
var b paramReconfigResponse
298+
_, err = b.unmarshal(badLen)
299+
300+
assert.Error(t, err)
301+
assert.ErrorIs(t, err, ErrReconfigRespParamInvalidLength)
302+
}

0 commit comments

Comments
 (0)