Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 60 additions & 8 deletions param_reconfig_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import (
)

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

type paramReconfigResponse struct {
paramHeader

// This value is copied from the request parameter and is used by the
// receiver of the Re-configuration Response Parameter to tie the
// response to the request.
reconfigResponseSequenceNumber uint32

// This value describes the result of the processing of the request.
result reconfigResult

// Optional fields (RFC 6525).
senderNextTSNPresent bool
senderNextTSN uint32
receiverNextTSNPresent bool
receiverNextTSN uint32
}

type reconfigResult uint32
Expand All @@ -50,7 +58,9 @@ const (

// Reconfiguration response errors.
var (
ErrReconfigRespParamTooShort = errors.New("reconfig response parameter too short")
ErrReconfigRespParamTooShort = errors.New("reconfig response parameter too short")
ErrReconfigRespParamInvalidLength = errors.New("reconfig response parameter invalid length")
ErrReconfigRespParamInvalidCombo = errors.New("receiverNextTSN present requires senderNextTSN present")
)

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

func (r *paramReconfigResponse) marshal() ([]byte, error) {
r.typ = reconfigResp
r.raw = make([]byte, 8)
binary.BigEndian.PutUint32(r.raw, r.reconfigResponseSequenceNumber)

// Enforce ordering: receiverNextTSN can only be present if senderNextTSN is present.
if r.receiverNextTSNPresent && !r.senderNextTSNPresent {
return nil, ErrReconfigRespParamInvalidCombo
}

valueLen := 8
if r.senderNextTSNPresent {
valueLen += 4
}
if r.receiverNextTSNPresent {
valueLen += 4
}

r.raw = make([]byte, valueLen)
binary.BigEndian.PutUint32(r.raw[0:], r.reconfigResponseSequenceNumber)
binary.BigEndian.PutUint32(r.raw[4:], uint32(r.result))

off := 8
if r.senderNextTSNPresent {
binary.BigEndian.PutUint32(r.raw[off:], r.senderNextTSN)
off += 4
}

if r.receiverNextTSNPresent {
binary.BigEndian.PutUint32(r.raw[off:], r.receiverNextTSN)
}

return r.paramHeader.marshal()
}

func (r *paramReconfigResponse) unmarshal(raw []byte) (param, error) {
err := r.paramHeader.unmarshal(raw)
if err != nil {
if err := r.paramHeader.unmarshal(raw); err != nil {
return nil, err
}

if len(r.raw) < 8 {
return nil, ErrReconfigRespParamTooShort
}

switch len(r.raw) {
case 8, 12, 16:
default:
return nil, ErrReconfigRespParamInvalidLength
}

r.reconfigResponseSequenceNumber = binary.BigEndian.Uint32(r.raw)
r.result = reconfigResult(binary.BigEndian.Uint32(r.raw[4:]))

// Optional fields: Sender's Next TSN, Receiver's Next TSN.
if len(r.raw) >= 12 {
r.senderNextTSNPresent = true
r.senderNextTSN = binary.BigEndian.Uint32(r.raw[8:])
}

if len(r.raw) == 16 {
r.receiverNextTSNPresent = true
r.receiverNextTSN = binary.BigEndian.Uint32(r.raw[12:])
}

return r, nil
}
223 changes: 222 additions & 1 deletion param_reconfig_response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package sctp

import (
"encoding/binary"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -60,7 +61,7 @@ func TestParamReconfigResponse_Failure(t *testing.T) {
}
}

func TestReconfigResultStringer(t *testing.T) {
func TestReconfigResultString(t *testing.T) {
tt := []struct {
result reconfigResult
expected string
Expand All @@ -79,3 +80,223 @@ func TestReconfigResultStringer(t *testing.T) {
assert.Equalf(t, tc.expected, actual, "Test case %d", i)
}
}

func TestParamReconfigResponse_OptionalFields(t *testing.T) {
type tc struct {
name string
raw []byte
wantRSN uint32
wantResult reconfigResult
wantSenderPresent bool
wantSender uint32
wantReceiverPresent bool
wantReceiver uint32
wantParamHeaderLen int
}

// build a parameter with optional fields.
build := func(rsn uint32, res reconfigResult, sender *uint32, receiver *uint32) []byte {
// +4 if sender and +4 if receiver)
valLen := 8
if sender != nil {
valLen += 4
}

if receiver != nil {
valLen += 4
}

total := 4 + valLen // header + value
b := make([]byte, total)

// header
binary.BigEndian.PutUint16(b[0:], uint16(reconfigResp))
binary.BigEndian.PutUint16(b[2:], uint16(total)) //nolint:gosec

// value
binary.BigEndian.PutUint32(b[4:], rsn)
binary.BigEndian.PutUint32(b[8:], uint32(res))

off := 12
if sender != nil {
binary.BigEndian.PutUint32(b[off:], *sender)
off += 4
}

if receiver != nil {
binary.BigEndian.PutUint32(b[off:], *receiver)
}

return b
}

senderOnly := uint32(0x01020304)
bothSender := uint32(0xA1B2C3D4)
bothReceiver := uint32(0x0A0B0C0D)

tests := []tc{
{
name: "sender_next_only",
raw: build(0x0000002A, reconfigResultErrorWrongSSN, &senderOnly, nil),
wantRSN: 0x0000002A,
wantResult: reconfigResultErrorWrongSSN,
wantSenderPresent: true,
wantSender: senderOnly,
wantReceiverPresent: false,
wantParamHeaderLen: 16, // 4 (hdr) + 12 (value)
},
{
name: "sender_and_receiver_next",
raw: build(0x00000007, reconfigResultSuccessPerformed, &bothSender, &bothReceiver),
wantRSN: 0x00000007,
wantResult: reconfigResultSuccessPerformed,
wantSenderPresent: true,
wantSender: bothSender,
wantReceiverPresent: true,
wantReceiver: bothReceiver,
wantParamHeaderLen: 20, // 4 (hdr) + 16 (value)
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var param paramReconfigResponse

_, err := param.unmarshal(tt.raw)
assert.NoError(t, err)

assert.Equal(t, reconfigResp, param.paramHeader.typ)
assert.Equal(t, tt.wantParamHeaderLen, param.paramHeader.len)
// param.paramHeader.raw should equal just the value part
assert.Equal(t, tt.raw[4:], param.paramHeader.raw)

assert.Equal(t, tt.wantRSN, param.reconfigResponseSequenceNumber)
assert.Equal(t, tt.wantResult, param.result)
assert.Equal(t, tt.wantSenderPresent, param.senderNextTSNPresent)

if tt.wantSenderPresent {
assert.Equal(t, tt.wantSender, param.senderNextTSN)
}

assert.Equal(t, tt.wantReceiverPresent, param.receiverNextTSNPresent)

if tt.wantReceiverPresent {
assert.Equal(t, tt.wantReceiver, param.receiverNextTSN)
}

out, err := param.marshal()
assert.NoError(t, err)
assert.Equal(t, tt.raw, out)
})
}
}

func TestParamReconfigResponse_OptionalFields_Roundtrip(t *testing.T) {
tcs := []struct {
name string
input paramReconfigResponse
valLen int // value length excluding the 4-byte header (8/12/16)
}{
{
name: "NoOptionals",
input: paramReconfigResponse{
reconfigResponseSequenceNumber: 0x0A,
result: reconfigResultSuccessPerformed,
},
valLen: 8,
},
{
name: "SenderOnly",
input: paramReconfigResponse{
reconfigResponseSequenceNumber: 0x0B,
result: reconfigResultDenied,
senderNextTSNPresent: true,
senderNextTSN: 0x12345678,
},
valLen: 12,
},
{
name: "SenderAndReceiver",
input: paramReconfigResponse{
reconfigResponseSequenceNumber: 0x0C,
result: reconfigResultInProgress,
senderNextTSNPresent: true,
senderNextTSN: 0x11111111,
receiverNextTSNPresent: true,
receiverNextTSN: 0x22222222,
},
valLen: 16,
},
}

for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
b, err := tc.input.marshal()
assert.NoError(t, err)
assert.GreaterOrEqual(t, len(b), 4)

pt := binary.BigEndian.Uint16(b[0:2])
pl := binary.BigEndian.Uint16(b[2:4])

assert.Equal(t, uint16(reconfigResp), pt)
assert.Equal(t, uint16(4+tc.valLen), pl) //nolint:gosec
assert.Equal(t, int(pl), len(b))

var out paramReconfigResponse
p, err := out.unmarshal(b)

assert.NoError(t, err)
got, ok := p.(*paramReconfigResponse)
assert.True(t, ok, "expected *paramReconfigResponse, got %T", p)

assert.Equal(t, tc.input.reconfigResponseSequenceNumber, got.reconfigResponseSequenceNumber)
assert.Equal(t, tc.input.result, got.result)
assert.Equal(t, tc.input.senderNextTSNPresent, got.senderNextTSNPresent)
assert.Equal(t, tc.input.senderNextTSN, got.senderNextTSN)
assert.Equal(t, tc.input.receiverNextTSNPresent, got.receiverNextTSNPresent)
assert.Equal(t, tc.input.receiverNextTSN, got.receiverNextTSN)

b2, err := got.marshal()
assert.NoError(t, err)
assert.Equal(t, b, b2)
})
}
}

func TestParamReconfigResponse_Marshal_OrderingGuard(t *testing.T) {
// receiver present without sender present must fail
r := paramReconfigResponse{
reconfigResponseSequenceNumber: 1,
result: reconfigResultSuccessPerformed,
receiverNextTSNPresent: true,
receiverNextTSN: 7,
}

_, err := r.marshal()
assert.Error(t, err)
assert.ErrorIs(t, err, ErrReconfigRespParamInvalidCombo)
}

func TestParamReconfigResponse_Unmarshal_InvalidLengths(t *testing.T) {
// too short
badShort := make([]byte, 10)
binary.BigEndian.PutUint16(badShort[0:], uint16(reconfigResp))
binary.BigEndian.PutUint16(badShort[2:], uint16(10))

var a paramReconfigResponse
_, err := a.unmarshal(badShort)

assert.Error(t, err)
assert.ErrorIs(t, err, ErrReconfigRespParamTooShort)

// invalid value length
badLen := make([]byte, 14)
binary.BigEndian.PutUint16(badLen[0:], uint16(reconfigResp))
binary.BigEndian.PutUint16(badLen[2:], uint16(14))

var b paramReconfigResponse
_, err = b.unmarshal(badLen)

assert.Error(t, err)
assert.ErrorIs(t, err, ErrReconfigRespParamInvalidLength)
}
Loading