blob: 3cc56f4e445deb12404e33e61752fb4eea80342d [file] [log] [blame]
// 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
}