// Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build go1.21 package quic import ( "encoding/binary" "net/netip" "time" ) // transportParameters transferred in the quic_transport_parameters TLS extension. // https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2 type transportParameters struct { originalDstConnID []byte maxIdleTimeout time.Duration statelessResetToken []byte maxUDPPayloadSize int64 initialMaxData int64 initialMaxStreamDataBidiLocal int64 initialMaxStreamDataBidiRemote int64 initialMaxStreamDataUni int64 initialMaxStreamsBidi int64 initialMaxStreamsUni int64 ackDelayExponent int8 maxAckDelay time.Duration disableActiveMigration bool preferredAddrV4 netip.AddrPort preferredAddrV6 netip.AddrPort preferredAddrConnID []byte preferredAddrResetToken []byte activeConnIDLimit int64 initialSrcConnID []byte retrySrcConnID []byte } const ( defaultParamMaxUDPPayloadSize = 65527 defaultParamAckDelayExponent = 3 defaultParamMaxAckDelayMilliseconds = 25 defaultParamActiveConnIDLimit = 2 ) // defaultTransportParameters is initialized to the RFC 9000 default values. func defaultTransportParameters() transportParameters { return transportParameters{ maxUDPPayloadSize: defaultParamMaxUDPPayloadSize, ackDelayExponent: defaultParamAckDelayExponent, maxAckDelay: defaultParamMaxAckDelayMilliseconds * time.Millisecond, activeConnIDLimit: defaultParamActiveConnIDLimit, } } const ( paramOriginalDestinationConnectionID = 0x00 paramMaxIdleTimeout = 0x01 paramStatelessResetToken = 0x02 paramMaxUDPPayloadSize = 0x03 paramInitialMaxData = 0x04 paramInitialMaxStreamDataBidiLocal = 0x05 paramInitialMaxStreamDataBidiRemote = 0x06 paramInitialMaxStreamDataUni = 0x07 paramInitialMaxStreamsBidi = 0x08 paramInitialMaxStreamsUni = 0x09 paramAckDelayExponent = 0x0a paramMaxAckDelay = 0x0b paramDisableActiveMigration = 0x0c paramPreferredAddress = 0x0d paramActiveConnectionIDLimit = 0x0e paramInitialSourceConnectionID = 0x0f paramRetrySourceConnectionID = 0x10 ) func marshalTransportParameters(p transportParameters) []byte { var b []byte if v := p.originalDstConnID; v != nil { b = appendVarint(b, paramOriginalDestinationConnectionID) b = appendVarintBytes(b, v) } if v := uint64(p.maxIdleTimeout / time.Millisecond); v != 0 { b = appendVarint(b, paramMaxIdleTimeout) b = appendVarint(b, uint64(sizeVarint(v))) b = appendVarint(b, uint64(v)) } if v := p.statelessResetToken; v != nil { b = appendVarint(b, paramStatelessResetToken) b = appendVarintBytes(b, v) } if v := p.maxUDPPayloadSize; v != defaultParamMaxUDPPayloadSize { b = appendVarint(b, paramMaxUDPPayloadSize) b = appendVarint(b, uint64(sizeVarint(uint64(v)))) b = appendVarint(b, uint64(v)) } if v := p.initialMaxData; v != 0 { b = appendVarint(b, paramInitialMaxData) b = appendVarint(b, uint64(sizeVarint(uint64(v)))) b = appendVarint(b, uint64(v)) } if v := p.initialMaxStreamDataBidiLocal; v != 0 { b = appendVarint(b, paramInitialMaxStreamDataBidiLocal) b = appendVarint(b, uint64(sizeVarint(uint64(v)))) b = appendVarint(b, uint64(v)) } if v := p.initialMaxStreamDataBidiRemote; v != 0 { b = appendVarint(b, paramInitialMaxStreamDataBidiRemote) b = appendVarint(b, uint64(sizeVarint(uint64(v)))) b = appendVarint(b, uint64(v)) } if v := p.initialMaxStreamDataUni; v != 0 { b = appendVarint(b, paramInitialMaxStreamDataUni) b = appendVarint(b, uint64(sizeVarint(uint64(v)))) b = appendVarint(b, uint64(v)) } if v := p.initialMaxStreamsBidi; v != 0 { b = appendVarint(b, paramInitialMaxStreamsBidi) b = appendVarint(b, uint64(sizeVarint(uint64(v)))) b = appendVarint(b, uint64(v)) } if v := p.initialMaxStreamsUni; v != 0 { b = appendVarint(b, paramInitialMaxStreamsUni) b = appendVarint(b, uint64(sizeVarint(uint64(v)))) b = appendVarint(b, uint64(v)) } if v := p.ackDelayExponent; v != defaultParamAckDelayExponent { b = appendVarint(b, paramAckDelayExponent) b = appendVarint(b, uint64(sizeVarint(uint64(v)))) b = appendVarint(b, uint64(v)) } if v := uint64(p.maxAckDelay / time.Millisecond); v != defaultParamMaxAckDelayMilliseconds { b = appendVarint(b, paramMaxAckDelay) b = appendVarint(b, uint64(sizeVarint(v))) b = appendVarint(b, v) } if p.disableActiveMigration { b = appendVarint(b, paramDisableActiveMigration) b = append(b, 0) // 0-length value } if p.preferredAddrConnID != nil { b = append(b, paramPreferredAddress) b = appendVarint(b, uint64(4+2+16+2+1+len(p.preferredAddrConnID)+16)) b = append(b, p.preferredAddrV4.Addr().AsSlice()...) // 4 bytes b = binary.BigEndian.AppendUint16(b, p.preferredAddrV4.Port()) // 2 bytes b = append(b, p.preferredAddrV6.Addr().AsSlice()...) // 16 bytes b = binary.BigEndian.AppendUint16(b, p.preferredAddrV6.Port()) // 2 bytes b = appendUint8Bytes(b, p.preferredAddrConnID) // 1 byte + len(conn_id) b = append(b, p.preferredAddrResetToken...) // 16 bytes } if v := p.activeConnIDLimit; v != defaultParamActiveConnIDLimit { b = appendVarint(b, paramActiveConnectionIDLimit) b = appendVarint(b, uint64(sizeVarint(uint64(v)))) b = appendVarint(b, uint64(v)) } if v := p.initialSrcConnID; v != nil { b = appendVarint(b, paramInitialSourceConnectionID) b = appendVarintBytes(b, v) } if v := p.retrySrcConnID; v != nil { b = appendVarint(b, paramRetrySourceConnectionID) b = appendVarintBytes(b, v) } return b } func unmarshalTransportParams(params []byte) (transportParameters, error) { p := defaultTransportParameters() for len(params) > 0 { id, n := consumeVarint(params) if n < 0 { return p, localTransportError{code: errTransportParameter} } params = params[n:] val, n := consumeVarintBytes(params) if n < 0 { return p, localTransportError{code: errTransportParameter} } params = params[n:] n = 0 switch id { case paramOriginalDestinationConnectionID: p.originalDstConnID = val n = len(val) case paramMaxIdleTimeout: var v uint64 v, n = consumeVarint(val) // If this is unreasonably large, consider it as no timeout to avoid // time.Duration overflows. if v > 1<<32 { v = 0 } p.maxIdleTimeout = time.Duration(v) * time.Millisecond case paramStatelessResetToken: if len(val) != 16 { return p, localTransportError{code: errTransportParameter} } p.statelessResetToken = val n = 16 case paramMaxUDPPayloadSize: p.maxUDPPayloadSize, n = consumeVarintInt64(val) if p.maxUDPPayloadSize < 1200 { return p, localTransportError{code: errTransportParameter} } case paramInitialMaxData: p.initialMaxData, n = consumeVarintInt64(val) case paramInitialMaxStreamDataBidiLocal: p.initialMaxStreamDataBidiLocal, n = consumeVarintInt64(val) case paramInitialMaxStreamDataBidiRemote: p.initialMaxStreamDataBidiRemote, n = consumeVarintInt64(val) case paramInitialMaxStreamDataUni: p.initialMaxStreamDataUni, n = consumeVarintInt64(val) case paramInitialMaxStreamsBidi: p.initialMaxStreamsBidi, n = consumeVarintInt64(val) if p.initialMaxStreamsBidi > maxStreamsLimit { return p, localTransportError{code: errTransportParameter} } case paramInitialMaxStreamsUni: p.initialMaxStreamsUni, n = consumeVarintInt64(val) if p.initialMaxStreamsUni > maxStreamsLimit { return p, localTransportError{code: errTransportParameter} } case paramAckDelayExponent: var v uint64 v, n = consumeVarint(val) if v > 20 { return p, localTransportError{code: errTransportParameter} } p.ackDelayExponent = int8(v) case paramMaxAckDelay: var v uint64 v, n = consumeVarint(val) if v >= 1<<14 { return p, localTransportError{code: errTransportParameter} } p.maxAckDelay = time.Duration(v) * time.Millisecond case paramDisableActiveMigration: p.disableActiveMigration = true case paramPreferredAddress: if len(val) < 4+2+16+2+1 { return p, localTransportError{code: errTransportParameter} } p.preferredAddrV4 = netip.AddrPortFrom( netip.AddrFrom4(*(*[4]byte)(val[:4])), binary.BigEndian.Uint16(val[4:][:2]), ) val = val[4+2:] p.preferredAddrV6 = netip.AddrPortFrom( netip.AddrFrom16(*(*[16]byte)(val[:16])), binary.BigEndian.Uint16(val[16:][:2]), ) val = val[16+2:] var nn int p.preferredAddrConnID, nn = consumeUint8Bytes(val) if nn < 0 { return p, localTransportError{code: errTransportParameter} } val = val[nn:] if len(val) != 16 { return p, localTransportError{code: errTransportParameter} } p.preferredAddrResetToken = val val = nil case paramActiveConnectionIDLimit: p.activeConnIDLimit, n = consumeVarintInt64(val) if p.activeConnIDLimit < 2 { return p, localTransportError{code: errTransportParameter} } case paramInitialSourceConnectionID: p.initialSrcConnID = val n = len(val) case paramRetrySourceConnectionID: p.retrySrcConnID = val n = len(val) default: n = len(val) } if n != len(val) { return p, localTransportError{code: errTransportParameter} } } return p, nil }