44package sctp // nolint:dupl
55
66import (
7+ "encoding/binary"
78 "errors"
89 "fmt"
910)
@@ -21,10 +22,14 @@ See chunkInitCommon for the fixed headers
2122 Reserved for ECN Capable (Note 2) Optional 32768 (0x8000)
2223 Host Name IP (Note 3) Optional 11
2324 Supported IP Types (Note 4) Optional 12
25+ Zero Checksum Acceptable (RFC 9653) Optional 32769 (0x8001)
2426*/
2527type chunkInit struct {
2628 chunkHeader
2729 chunkInitCommon
30+ // RFC 9653 Zero Checksum Acceptable negotiation
31+ zcaPresent bool
32+ zcaEDMID uint32
2833}
2934
3035// Init chunk errors.
@@ -63,6 +68,13 @@ func (i *chunkInit) unmarshal(raw []byte) error {
6368 return fmt .Errorf ("%w: %v" , ErrChunkTypeInitUnmarshalFailed , err ) //nolint:errorlint
6469 }
6570
71+ // Parse RFC 9653 ZCA TLV (optional, at most once). Parameters start
72+ // immediately after the fixed INIT common body.
73+ if len (i .raw ) >= initChunkMinLength {
74+ present , edmid := scanZCAParamBytes (i .raw )
75+ i .zcaPresent , i .zcaEDMID = present , edmid
76+ }
77+
6678 return nil
6779}
6880
@@ -72,6 +84,13 @@ func (i *chunkInit) marshal() ([]byte, error) {
7284 return nil , fmt .Errorf ("%w: %v" , ErrChunkTypeInitMarshalFailed , err ) //nolint:errorlint
7385 }
7486
87+ // Optionally append ZCA (avoid duplicates if chunkInitCommon already added one).
88+ if i .zcaPresent {
89+ if ok , _ := scanZCAParamBytes (initShared ); ! ok {
90+ initShared = appendZCAParam (initShared , i .zcaEDMID )
91+ }
92+ }
93+
7594 i .chunkHeader .typ = ctInit
7695 i .chunkHeader .raw = initShared
7796
@@ -141,3 +160,56 @@ func (i *chunkInit) check() (abort bool, err error) {
141160func (i * chunkInit ) String () string {
142161 return fmt .Sprintf ("%s\n %s" , i .chunkHeader , i .chunkInitCommon )
143162}
163+
164+ // ZeroChecksumEDMID returns (EDMID, present) negotiated via RFC 9653 ZCA.
165+ func (i * chunkInit ) ZeroChecksumEDMID () (uint32 , bool ) {
166+ return i .zcaEDMID , i .zcaPresent
167+ }
168+
169+ // scanZCAParamBytes scans a parameter list (in the INIT/INIT-ACK value bytes)
170+ // for a single ZCA TLV and returns (isPresent, edmid, isDup).
171+ func scanZCAParamBytes (b []byte ) (bool , uint32 ) {
172+ const typZCA = uint16 (zeroChecksumAcceptable )
173+
174+ off := initFixedValueLen
175+ for {
176+ if off + 4 > len (b ) { // not enough for a param header
177+ return false , 0
178+ }
179+
180+ typ := binary .BigEndian .Uint16 (b [off : off + 2 ])
181+ length := int (binary .BigEndian .Uint16 (b [off + 2 : off + 4 ]))
182+
183+ if length < 4 || off + length > len (b ) { // malformed/truncated
184+ return false , 0
185+ }
186+
187+ if typ == typZCA && length == 8 {
188+ edmid := binary .BigEndian .Uint32 (b [off + 4 : off + 8 ])
189+
190+ return true , edmid
191+ }
192+
193+ nxt := off + length
194+
195+ if rem := length & 3 ; rem != 0 { // 4-byte padding
196+ nxt += 4 - rem
197+ }
198+
199+ if nxt <= off || nxt > len (b ) {
200+ return false , 0
201+ }
202+
203+ off = nxt
204+ }
205+ }
206+
207+ // appendZCAParam appends a single ZCA TLV.
208+ func appendZCAParam (dst []byte , edmid uint32 ) []byte {
209+ var tlv [8 ]byte
210+ binary .BigEndian .PutUint16 (tlv [0 :2 ], uint16 (zeroChecksumAcceptable ))
211+ binary .BigEndian .PutUint16 (tlv [2 :4 ], 8 )
212+ binary .BigEndian .PutUint32 (tlv [4 :8 ], edmid )
213+
214+ return append (dst , tlv [:]... )
215+ }
0 commit comments