// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package sctp import ( "encoding/binary" "errors" "fmt" ) /* chunkSelectiveAck represents an SCTP Chunk of type SACK This chunk is sent to the peer endpoint to acknowledge received DATA chunks and to inform the peer endpoint of gaps in the received subsequences of DATA chunks as represented by their TSNs. 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 = 3 |Chunk Flags | Chunk Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Cumulative TSN Ack | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Advertised Receiver Window Credit (a_rwnd) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Number of Gap Ack Blocks = N | Number of Duplicate TSNs = X | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Gap Ack Block #1 Start | Gap Ack Block #1 End | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ / / \ ... \ / / +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Gap Ack Block #N Start | Gap Ack Block #N End | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Duplicate TSN 1 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ / / \ ... \ / / +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Duplicate TSN X | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ type gapAckBlock struct { start uint16 end uint16 } // Selective ack chunk errors var ( ErrChunkTypeNotSack = errors.New("ChunkType is not of type SACK") ErrSackSizeNotLargeEnoughInfo = errors.New("SACK Chunk size is not large enough to contain header") ErrSackSizeNotMatchPredicted = errors.New("SACK Chunk size does not match predicted amount from header values") ) // String makes gapAckBlock printable func (g gapAckBlock) String() string { return fmt.Sprintf("%d - %d", g.start, g.end) } type chunkSelectiveAck struct { chunkHeader cumulativeTSNAck uint32 advertisedReceiverWindowCredit uint32 gapAckBlocks []gapAckBlock duplicateTSN []uint32 } const ( selectiveAckHeaderSize = 12 ) func (s *chunkSelectiveAck) unmarshal(raw []byte) error { if err := s.chunkHeader.unmarshal(raw); err != nil { return err } if s.typ != ctSack { return fmt.Errorf("%w: actually is %s", ErrChunkTypeNotSack, s.typ.String()) } if len(s.raw) < selectiveAckHeaderSize { return fmt.Errorf("%w: %v remaining, needs %v bytes", ErrSackSizeNotLargeEnoughInfo, len(s.raw), selectiveAckHeaderSize) } s.cumulativeTSNAck = binary.BigEndian.Uint32(s.raw[0:]) s.advertisedReceiverWindowCredit = binary.BigEndian.Uint32(s.raw[4:]) s.gapAckBlocks = make([]gapAckBlock, binary.BigEndian.Uint16(s.raw[8:])) s.duplicateTSN = make([]uint32, binary.BigEndian.Uint16(s.raw[10:])) if len(s.raw) != selectiveAckHeaderSize+(4*len(s.gapAckBlocks)+(4*len(s.duplicateTSN))) { return ErrSackSizeNotMatchPredicted } offset := selectiveAckHeaderSize for i := range s.gapAckBlocks { s.gapAckBlocks[i].start = binary.BigEndian.Uint16(s.raw[offset:]) s.gapAckBlocks[i].end = binary.BigEndian.Uint16(s.raw[offset+2:]) offset += 4 } for i := range s.duplicateTSN { s.duplicateTSN[i] = binary.BigEndian.Uint32(s.raw[offset:]) offset += 4 } return nil } func (s *chunkSelectiveAck) marshal() ([]byte, error) { sackRaw := make([]byte, selectiveAckHeaderSize+(4*len(s.gapAckBlocks)+(4*len(s.duplicateTSN)))) binary.BigEndian.PutUint32(sackRaw[0:], s.cumulativeTSNAck) binary.BigEndian.PutUint32(sackRaw[4:], s.advertisedReceiverWindowCredit) binary.BigEndian.PutUint16(sackRaw[8:], uint16(len(s.gapAckBlocks))) binary.BigEndian.PutUint16(sackRaw[10:], uint16(len(s.duplicateTSN))) offset := selectiveAckHeaderSize for _, g := range s.gapAckBlocks { binary.BigEndian.PutUint16(sackRaw[offset:], g.start) binary.BigEndian.PutUint16(sackRaw[offset+2:], g.end) offset += 4 } for _, t := range s.duplicateTSN { binary.BigEndian.PutUint32(sackRaw[offset:], t) offset += 4 } s.chunkHeader.typ = ctSack s.chunkHeader.raw = sackRaw return s.chunkHeader.marshal() } func (s *chunkSelectiveAck) check() (abort bool, err error) { return false, nil } // String makes chunkSelectiveAck printable func (s *chunkSelectiveAck) String() string { res := fmt.Sprintf("SACK cumTsnAck=%d arwnd=%d dupTsn=%d", s.cumulativeTSNAck, s.advertisedReceiverWindowCredit, s.duplicateTSN) for _, gap := range s.gapAckBlocks { res = fmt.Sprintf("%s\n gap ack: %s", res, gap) } return res }