168 lines
5.8 KiB
Go
168 lines
5.8 KiB
Go
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package sctp
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
/*
|
|
chunkInitCommon represents an SCTP Chunk body of type INIT and INIT ACK
|
|
|
|
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
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| Type = 1 | Chunk Flags | Chunk Length |
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| Initiate Tag |
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| Advertised Receiver Window Credit (a_rwnd) |
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| Number of Outbound Streams | Number of Inbound Streams |
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| Initial TSN |
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| |
|
|
| Optional/Variable-Length Parameters |
|
|
| |
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
|
|
The INIT chunk contains the following parameters. Unless otherwise
|
|
noted, each parameter MUST only be included once in the INIT chunk.
|
|
|
|
Fixed Parameters Status
|
|
----------------------------------------------
|
|
Initiate Tag Mandatory
|
|
Advertised Receiver Window Credit Mandatory
|
|
Number of Outbound Streams Mandatory
|
|
Number of Inbound Streams Mandatory
|
|
Initial TSN Mandatory
|
|
*/
|
|
|
|
type chunkInitCommon struct {
|
|
initiateTag uint32
|
|
advertisedReceiverWindowCredit uint32
|
|
numOutboundStreams uint16
|
|
numInboundStreams uint16
|
|
initialTSN uint32
|
|
params []param
|
|
unrecognizedParams []paramHeader
|
|
}
|
|
|
|
const (
|
|
initChunkMinLength = 16
|
|
initOptionalVarHeaderLength = 4
|
|
)
|
|
|
|
// Init chunk errors
|
|
var (
|
|
ErrInitChunkParseParamTypeFailed = errors.New("failed to parse param type")
|
|
ErrInitAckMarshalParam = errors.New("unable to marshal parameter for INIT/INITACK")
|
|
)
|
|
|
|
func (i *chunkInitCommon) unmarshal(raw []byte) error {
|
|
i.initiateTag = binary.BigEndian.Uint32(raw[0:])
|
|
i.advertisedReceiverWindowCredit = binary.BigEndian.Uint32(raw[4:])
|
|
i.numOutboundStreams = binary.BigEndian.Uint16(raw[8:])
|
|
i.numInboundStreams = binary.BigEndian.Uint16(raw[10:])
|
|
i.initialTSN = binary.BigEndian.Uint32(raw[12:])
|
|
|
|
// https://tools.ietf.org/html/rfc4960#section-3.2.1
|
|
//
|
|
// Chunk values of SCTP control chunks consist of a chunk-type-specific
|
|
// header of required fields, followed by zero or more parameters. The
|
|
// optional and variable-length parameters contained in a chunk are
|
|
// defined in a Type-Length-Value format as shown below.
|
|
//
|
|
// 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 | Parameter Length |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | |
|
|
// | Parameter Value |
|
|
// | |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
|
|
offset := initChunkMinLength
|
|
remaining := len(raw) - offset
|
|
for remaining > 0 {
|
|
if remaining > initOptionalVarHeaderLength {
|
|
var pHeader paramHeader
|
|
if err := pHeader.unmarshal(raw[offset:]); err != nil {
|
|
return fmt.Errorf("%w: %v", ErrInitChunkParseParamTypeFailed, err) //nolint:errorlint
|
|
}
|
|
|
|
p, err := buildParam(pHeader.typ, raw[offset:])
|
|
if err != nil {
|
|
i.unrecognizedParams = append(i.unrecognizedParams, pHeader)
|
|
} else {
|
|
i.params = append(i.params, p)
|
|
}
|
|
|
|
padding := getPadding(pHeader.length())
|
|
offset += pHeader.length() + padding
|
|
remaining -= pHeader.length() + padding
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (i *chunkInitCommon) marshal() ([]byte, error) {
|
|
out := make([]byte, initChunkMinLength)
|
|
binary.BigEndian.PutUint32(out[0:], i.initiateTag)
|
|
binary.BigEndian.PutUint32(out[4:], i.advertisedReceiverWindowCredit)
|
|
binary.BigEndian.PutUint16(out[8:], i.numOutboundStreams)
|
|
binary.BigEndian.PutUint16(out[10:], i.numInboundStreams)
|
|
binary.BigEndian.PutUint32(out[12:], i.initialTSN)
|
|
for idx, p := range i.params {
|
|
pp, err := p.marshal()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: %v", ErrInitAckMarshalParam, err) //nolint:errorlint
|
|
}
|
|
|
|
out = append(out, pp...)
|
|
|
|
// Chunks (including Type, Length, and Value fields) are padded out
|
|
// by the sender with all zero bytes to be a multiple of 4 bytes
|
|
// long. This padding MUST NOT be more than 3 bytes in total. The
|
|
// Chunk Length value does not include terminating padding of the
|
|
// chunk. *However, it does include padding of any variable-length
|
|
// parameter except the last parameter in the chunk.* The receiver
|
|
// MUST ignore the padding.
|
|
if idx != len(i.params)-1 {
|
|
out = padByte(out, getPadding(len(pp)))
|
|
}
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
// String makes chunkInitCommon printable
|
|
func (i chunkInitCommon) String() string {
|
|
format := `initiateTag: %d
|
|
advertisedReceiverWindowCredit: %d
|
|
numOutboundStreams: %d
|
|
numInboundStreams: %d
|
|
initialTSN: %d`
|
|
|
|
res := fmt.Sprintf(format,
|
|
i.initiateTag,
|
|
i.advertisedReceiverWindowCredit,
|
|
i.numOutboundStreams,
|
|
i.numInboundStreams,
|
|
i.initialTSN,
|
|
)
|
|
|
|
for i, param := range i.params {
|
|
res += fmt.Sprintf("Param %d:\n %s", i, param)
|
|
}
|
|
return res
|
|
}
|