mirror of
https://github.com/pion/webrtc.git
synced 2025-10-06 07:37:10 +08:00

* Add test for Packet marshal * Implement Packet, ChunkHeader, ChunkInitAck * Improve error messages, and de-dupe some comments
161 lines
4.5 KiB
Go
161 lines
4.5 KiB
Go
package sctp
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// ChunkType is an enum for SCTP Chunk Type field
|
|
// This field identifies the type of information contained in the
|
|
// Chunk Value field.
|
|
type ChunkType uint8
|
|
|
|
// List of known ChunkType enums
|
|
const (
|
|
Data ChunkType = 0
|
|
INIT ChunkType = 1
|
|
INITACK ChunkType = 2
|
|
SACK ChunkType = 3
|
|
HEARTBEAT ChunkType = 4
|
|
HEARTBEATACK ChunkType = 5
|
|
ABORT ChunkType = 6
|
|
SHUTDOWN ChunkType = 7
|
|
SHUTDOWNACK ChunkType = 8
|
|
ERROR ChunkType = 9
|
|
COOKIEECHO ChunkType = 10
|
|
COOKIEACK ChunkType = 11
|
|
CWR ChunkType = 13
|
|
SHUTDOWNCOMPLETE ChunkType = 14
|
|
)
|
|
|
|
func (c ChunkType) String() string {
|
|
switch c {
|
|
case Data:
|
|
return "Payload data"
|
|
case INIT:
|
|
return "Initiation"
|
|
case INITACK:
|
|
return "Initiation Acknowledgement"
|
|
case SACK:
|
|
return "Selective Acknowledgement"
|
|
case HEARTBEAT:
|
|
return "Heartbeat"
|
|
case HEARTBEATACK:
|
|
return "Heartbeat Acknowledgement"
|
|
case ABORT:
|
|
return "Abort"
|
|
case SHUTDOWN:
|
|
return "Shutdown"
|
|
case SHUTDOWNACK:
|
|
return "Shutdown Acknowledgement"
|
|
case ERROR:
|
|
return "Error"
|
|
case COOKIEECHO:
|
|
return "Cookie Echo"
|
|
case COOKIEACK:
|
|
return "Cookie Acknowledgement"
|
|
case CWR:
|
|
return "Congestion Window Reduced"
|
|
case SHUTDOWNCOMPLETE:
|
|
return "Shutdown Complete"
|
|
default:
|
|
return fmt.Sprintf("Unknown ChunkType: %d", c)
|
|
}
|
|
}
|
|
|
|
/*
|
|
ChunkHeader represents a SCTP Chunk header, defined in https://tools.ietf.org/html/rfc4960#section-3.2
|
|
The figure below illustrates the field format for the chunks to be
|
|
transmitted in the SCTP packet. Each chunk is formatted with a Chunk
|
|
Type field, a chunk-specific Flag field, a Chunk Length field, and a
|
|
Value field.
|
|
|
|
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
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| Chunk Type | Chunk Flags | Chunk Length |
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
| |
|
|
| Chunk Value |
|
|
| |
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
*/
|
|
type ChunkHeader struct {
|
|
typ ChunkType
|
|
Flags byte
|
|
Value []byte
|
|
}
|
|
|
|
const (
|
|
chunkHeaderSize = 4
|
|
)
|
|
|
|
// Unmarshal populates a ChunkHeader from a raw byte[]
|
|
func (c *ChunkHeader) Unmarshal(raw []byte) error {
|
|
if len(raw) < chunkHeaderSize {
|
|
return errors.Errorf("raw only %d bytes, %d is the minimum length for a SCTP chunk", len(raw), chunkHeaderSize)
|
|
}
|
|
|
|
c.typ = ChunkType(raw[0])
|
|
c.Flags = byte(raw[1])
|
|
length := binary.BigEndian.Uint16(raw[2:])
|
|
|
|
// Length includes Chunk header
|
|
valueLength := int(length - chunkHeaderSize)
|
|
lengthAfterValue := len(raw) - (chunkHeaderSize + int(valueLength))
|
|
|
|
if lengthAfterValue < 0 {
|
|
return errors.Errorf("Not enough data left in SCTP packet to satisfy requested length remain %d req %d ", valueLength, len(raw)-chunkHeaderSize)
|
|
} else if lengthAfterValue < 4 {
|
|
// https://tools.ietf.org/html/rfc4960#section-3.2
|
|
// The Chunk Length field does not count any chunk padding.
|
|
// 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.
|
|
for i := lengthAfterValue; i > 0; i-- {
|
|
paddingOffset := chunkHeaderSize + valueLength + (i - 1)
|
|
if raw[paddingOffset] != 0 {
|
|
return errors.Errorf("Chunk padding is non-zero at offset %d ", paddingOffset)
|
|
}
|
|
}
|
|
}
|
|
|
|
c.Value = raw[chunkHeaderSize : chunkHeaderSize+valueLength]
|
|
return nil
|
|
}
|
|
|
|
// Marshal populates a raw byte[] from a ChunkHeader
|
|
func (c *ChunkHeader) Marshal(valueLength int) ([]byte, error) {
|
|
raw := make([]byte, 4)
|
|
|
|
raw[0] = uint8(c.typ)
|
|
raw[1] = c.Flags
|
|
binary.BigEndian.PutUint16(raw[2:], uint16(valueLength+4))
|
|
return raw, nil
|
|
}
|
|
|
|
// Type returns the type of Chunk
|
|
func (c *ChunkHeader) Type() ChunkType {
|
|
return c.typ
|
|
}
|
|
|
|
func (c *ChunkHeader) valueLength() int {
|
|
return len(c.Value)
|
|
}
|
|
|
|
// Chunk represents an SCTP chunk
|
|
type Chunk interface {
|
|
Unmarshal(raw []byte) error
|
|
Marshal() ([]byte, error)
|
|
Type() ChunkType
|
|
Check() (bool, error)
|
|
|
|
valueLength() int
|
|
}
|