Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Unknown Parameters in INIT Chunk #298

Merged
merged 1 commit into from
Feb 9, 2024
Merged
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
20 changes: 12 additions & 8 deletions chunk_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
ErrInitInboundStreamRequestZero = errors.New("INIT ACK inbound stream request must be > 0")
ErrInitOutboundStreamRequestZero = errors.New("INIT ACK outbound stream request must be > 0")
ErrInitAdvertisedReceiver1500 = errors.New("INIT ACK Advertised Receiver Window Credit (a_rwnd) must be >= 1500")
ErrInitUnknownParam = errors.New("INIT with unknown param")
)

func (i *chunkInit) unmarshal(raw []byte) error {
Expand Down Expand Up @@ -89,8 +90,7 @@
// to be 0, the receiver MUST treat it as an error and close the
// association by transmitting an ABORT.
if i.initiateTag == 0 {
abort = true
return abort, ErrChunkTypeInitInitateTagZero
return true, ErrChunkTypeInitInitateTagZero

Check warning on line 93 in chunk_init.go

View check run for this annotation

Codecov / codecov/patch

chunk_init.go#L93

Added line #L93 was not covered by tests
}

// Defines the maximum number of streams the sender of this INIT
Expand All @@ -104,8 +104,7 @@
// Note: A receiver of an INIT with the MIS value of 0 SHOULD abort
// the association.
if i.numInboundStreams == 0 {
abort = true
return abort, ErrInitInboundStreamRequestZero
return true, ErrInitInboundStreamRequestZero

Check warning on line 107 in chunk_init.go

View check run for this annotation

Codecov / codecov/patch

chunk_init.go#L107

Added line #L107 was not covered by tests
}

// Defines the number of outbound streams the sender of this INIT
Expand All @@ -116,17 +115,22 @@
// abort the association.

if i.numOutboundStreams == 0 {
abort = true
return abort, ErrInitOutboundStreamRequestZero
return true, ErrInitOutboundStreamRequestZero

Check warning on line 118 in chunk_init.go

View check run for this annotation

Codecov / codecov/patch

chunk_init.go#L118

Added line #L118 was not covered by tests
}

// An SCTP receiver MUST be able to receive a minimum of 1500 bytes in
// one SCTP packet. This means that an SCTP endpoint MUST NOT indicate
// less than 1500 bytes in its initial a_rwnd sent in the INIT or INIT
// ACK.
if i.advertisedReceiverWindowCredit < 1500 {
abort = true
return abort, ErrInitAdvertisedReceiver1500
return true, ErrInitAdvertisedReceiver1500
}

Check warning on line 127 in chunk_init.go

View check run for this annotation

Codecov / codecov/patch

chunk_init.go#L126-L127

Added lines #L126 - L127 were not covered by tests

for _, p := range i.unrecognizedParams {
if p.unrecognizedAction == paramHeaderUnrecognizedActionStop ||
p.unrecognizedAction == paramHeaderUnrecognizedActionStopAndReport {
return true, ErrInitUnknownParam
}

Check warning on line 133 in chunk_init.go

View check run for this annotation

Codecov / codecov/patch

chunk_init.go#L130-L133

Added lines #L130 - L133 were not covered by tests
}

return false, nil
Expand Down
21 changes: 12 additions & 9 deletions chunk_init_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type chunkInitCommon struct {
numInboundStreams uint16
initialTSN uint32
params []param
unrecognizedParams []paramHeader
}

const (
Expand All @@ -59,7 +60,6 @@ const (
// Init chunk errors
var (
ErrInitChunkParseParamTypeFailed = errors.New("failed to parse param type")
ErrInitChunkUnmarshalParam = errors.New("failed unmarshalling param in Init Chunk")
ErrInitAckMarshalParam = errors.New("unable to marshal parameter for INIT/INITACK")
)

Expand Down Expand Up @@ -91,18 +91,21 @@ func (i *chunkInitCommon) unmarshal(raw []byte) error {
remaining := len(raw) - offset
for remaining > 0 {
if remaining > initOptionalVarHeaderLength {
pType, err := parseParamType(raw[offset:])
if err != nil {
var pHeader paramHeader
if err := pHeader.unmarshal(raw[offset:]); err != nil {
return fmt.Errorf("%w: %v", ErrInitChunkParseParamTypeFailed, err) //nolint:errorlint
}
p, err := buildParam(pType, raw[offset:])

p, err := buildParam(pHeader.typ, raw[offset:])
if err != nil {
return fmt.Errorf("%w: %v", ErrInitChunkUnmarshalParam, err) //nolint:errorlint
i.unrecognizedParams = append(i.unrecognizedParams, pHeader)
} else {
i.params = append(i.params, p)
}
i.params = append(i.params, p)
padding := getPadding(p.length())
offset += p.length() + padding
remaining -= p.length() + padding

padding := getPadding(pHeader.length())
offset += pHeader.length() + padding
remaining -= pHeader.length() + padding
} else {
break
}
Expand Down
35 changes: 35 additions & 0 deletions chunk_init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package sctp

import (
"testing"
)

func TestChunkInit_UnrecognizedParameters(t *testing.T) {
initChunkHeader := []byte{
0x55, 0xb9, 0x64, 0xa5, 0x00, 0x02, 0x00, 0x00,
0x04, 0x00, 0x08, 0x00, 0xe8, 0x6d, 0x10, 0x30,
}

unrecognizedSkip := append([]byte{}, initChunkHeader...)
unrecognizedSkip = append(unrecognizedSkip, byte(paramHeaderUnrecognizedActionSkip), 0xFF, 0x00, 0x04, 0x00)

i := &chunkInitCommon{}
if err := i.unmarshal(unrecognizedSkip); err != nil {
t.Errorf("Unmarshal init Chunk failed: %v", err)
} else if len(i.unrecognizedParams) != 1 || i.unrecognizedParams[0].unrecognizedAction != paramHeaderUnrecognizedActionSkip {
t.Errorf("Unrecognized Param parsed incorrectly")
}

unrecognizedStop := append([]byte{}, initChunkHeader...)
unrecognizedStop = append(unrecognizedStop, byte(paramHeaderUnrecognizedActionStop), 0xFF, 0x00, 0x04, 0x00)

i = &chunkInitCommon{}
if err := i.unmarshal(unrecognizedStop); err != nil {
t.Errorf("Unmarshal init Chunk failed: %v", err)
} else if len(i.unrecognizedParams) != 1 || i.unrecognizedParams[0].unrecognizedAction != paramHeaderUnrecognizedActionStop {
t.Errorf("Unrecognized Param parsed incorrectly")
}
}
7 changes: 4 additions & 3 deletions param_ecn_capable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ func TestParamECNCapabale_Success(t *testing.T) {
testParamECNCapabale(),
&paramECNCapable{
paramHeader: paramHeader{
typ: ecnCapable,
len: 4,
raw: []byte{},
typ: ecnCapable,
unrecognizedAction: paramHeaderUnrecognizedActionSkip,
len: 4,
raw: []byte{},
},
},
},
Expand Down
7 changes: 4 additions & 3 deletions param_forward_tsn_supported_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ func TestParamForwardTSNSupported_Success(t *testing.T) {
testParamForwardTSNSupported(),
&paramForwardTSNSupported{
paramHeader: paramHeader{
typ: forwardTSNSupp,
len: 4,
raw: []byte{},
typ: forwardTSNSupp,
len: 4,
unrecognizedAction: paramHeaderUnrecognizedActionSkipAndReport,
raw: []byte{},
},
},
},
Expand Down
34 changes: 31 additions & 3 deletions paramheader.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,40 @@ import (
"fmt"
)

type paramHeaderUnrecognizedAction byte

type paramHeader struct {
typ paramType
len int
raw []byte
typ paramType
unrecognizedAction paramHeaderUnrecognizedAction
len int
raw []byte
}

/*
The Parameter Types are encoded such that the highest-order 2 bits specify
the action that is taken if the processing endpoint does not recognize the
Parameter Type.

00 - Stop processing this parameter and do not process any further parameters within this chunk.

01 - Stop processing this parameter, do not process any further parameters within this chunk, and
report the unrecognized parameter, as described in Section 3.2.2.

10 - Skip this parameter and continue processing.

11 - Skip this parameter and continue processing, but report the unrecognized
parameter, as described in Section 3.2.2.

https://www.rfc-editor.org/rfc/rfc9260.html#section-3.2.1
*/

const (
paramHeaderUnrecognizedActionMask = 0b11000000
paramHeaderUnrecognizedActionStop paramHeaderUnrecognizedAction = 0b00000000
paramHeaderUnrecognizedActionStopAndReport paramHeaderUnrecognizedAction = 0b01000000
paramHeaderUnrecognizedActionSkip paramHeaderUnrecognizedAction = 0b10000000
paramHeaderUnrecognizedActionSkipAndReport paramHeaderUnrecognizedAction = 0b11000000

paramHeaderLength = 4
)

Expand Down Expand Up @@ -57,6 +84,7 @@ func (p *paramHeader) unmarshal(raw []byte) error {
return fmt.Errorf("%w: %v", ErrParamHeaderParseFailed, err) //nolint:errorlint
}
p.typ = typ
p.unrecognizedAction = paramHeaderUnrecognizedAction(raw[0] & paramHeaderUnrecognizedActionMask)
p.raw = raw[paramHeaderLength:paramLengthPlusHeader]
p.len = int(paramLengthPlusHeader)

Expand Down
Loading