quic: transport parameter encoding and decoding

Transport parameters are passed in the extension_data field of the
quic_transport_parameters TLS extension.

RFC 9000, Section 18.
RFC 9001, Section 8.2.

For golang/go#58547

Change-Id: I294ab6cdef19256f5db02dc269e8b417b1d5e54b
Reviewed-on: https://go-review.googlesource.com/c/net/+/510575
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Damien Neil <dneil@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/internal/quic/transport_params.go b/internal/quic/transport_params.go
new file mode 100644
index 0000000..416bfb8
--- /dev/null
+++ b/internal/quic/transport_params.go
@@ -0,0 +1,277 @@
+// 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               uint8
+	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(errTransportParameter)
+		}
+		params = params[n:]
+		val, n := consumeVarintBytes(params)
+		if n < 0 {
+			return p, localTransportError(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(errTransportParameter)
+			}
+			p.statelessResetToken = val
+			n = 16
+		case paramMaxUDPPayloadSize:
+			p.maxUDPPayloadSize, n = consumeVarintInt64(val)
+			if p.maxUDPPayloadSize < 1200 {
+				return p, localTransportError(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)
+		case paramInitialMaxStreamsUni:
+			p.initialMaxStreamsUni, n = consumeVarintInt64(val)
+		case paramAckDelayExponent:
+			var v uint64
+			v, n = consumeVarint(val)
+			if v > 20 {
+				return p, localTransportError(errTransportParameter)
+			}
+			p.ackDelayExponent = uint8(v)
+		case paramMaxAckDelay:
+			var v uint64
+			v, n = consumeVarint(val)
+			if v >= 1<<14 {
+				return p, localTransportError(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(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(errTransportParameter)
+			}
+			val = val[nn:]
+			if len(val) != 16 {
+				return p, localTransportError(errTransportParameter)
+			}
+			p.preferredAddrResetToken = val
+			val = nil
+		case paramActiveConnectionIDLimit:
+			p.activeConnIDLimit, n = consumeVarintInt64(val)
+			if p.activeConnIDLimit < 2 {
+				return p, localTransportError(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(errTransportParameter)
+		}
+	}
+	return p, nil
+}
diff --git a/internal/quic/transport_params_test.go b/internal/quic/transport_params_test.go
new file mode 100644
index 0000000..e1c45ca
--- /dev/null
+++ b/internal/quic/transport_params_test.go
@@ -0,0 +1,374 @@
+// 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 (
+	"bytes"
+	"math"
+	"net/netip"
+	"reflect"
+	"testing"
+	"time"
+)
+
+func TestTransportParametersMarshalUnmarshal(t *testing.T) {
+	for _, test := range []struct {
+		params func(p *transportParameters)
+		enc    []byte
+	}{{
+		params: func(p *transportParameters) {
+			p.originalDstConnID = []byte("connid")
+		},
+		enc: []byte{
+			0x00, // original_destination_connection_id
+			byte(len("connid")),
+			'c', 'o', 'n', 'n', 'i', 'd',
+		},
+	}, {
+		params: func(p *transportParameters) {
+			p.maxIdleTimeout = 10 * time.Millisecond
+		},
+		enc: []byte{
+			0x01, // max_idle_timeout
+			1,    // length
+			10,   // varint msecs
+		},
+	}, {
+		params: func(p *transportParameters) {
+			p.statelessResetToken = []byte("0123456789abcdef")
+		},
+		enc: []byte{
+			0x02, // stateless_reset_token
+			16,   // length
+			'0', '1', '2', '3', '4', '5', '6', '7',
+			'8', '9', 'a', 'b', 'c', 'd', 'e', 'f', // reset token
+		},
+	}, {
+		params: func(p *transportParameters) {
+			p.maxUDPPayloadSize = 1200
+		},
+		enc: []byte{
+			0x03,       // max_udp_payload_size
+			2,          // length
+			0x44, 0xb0, // varint value
+		},
+	}, {
+		params: func(p *transportParameters) {
+			p.initialMaxData = 10
+		},
+		enc: []byte{
+			0x04, // initial_max_data
+			1,    // length
+			10,   // varint value
+		},
+	}, {
+		params: func(p *transportParameters) {
+			p.initialMaxStreamDataBidiLocal = 10
+		},
+		enc: []byte{
+			0x05, // initial_max_stream_data_bidi_local
+			1,    // length
+			10,   // varint value
+		},
+	}, {
+		params: func(p *transportParameters) {
+			p.initialMaxStreamDataBidiRemote = 10
+		},
+		enc: []byte{
+			0x06, // initial_max_stream_data_bidi_remote
+			1,    // length
+			10,   // varint value
+		},
+	}, {
+		params: func(p *transportParameters) {
+			p.initialMaxStreamDataUni = 10
+		},
+		enc: []byte{
+			0x07, // initial_max_stream_data_uni
+			1,    // length
+			10,   // varint value
+		},
+	}, {
+		params: func(p *transportParameters) {
+			p.initialMaxStreamsBidi = 10
+		},
+		enc: []byte{
+			0x08, // initial_max_streams_bidi
+			1,    // length
+			10,   // varint value
+		},
+	}, {
+		params: func(p *transportParameters) {
+			p.initialMaxStreamsUni = 10
+		},
+		enc: []byte{
+			0x09, // initial_max_streams_uni
+			1,    // length
+			10,   // varint value
+		},
+	}, {
+		params: func(p *transportParameters) {
+			p.ackDelayExponent = 4
+		},
+		enc: []byte{
+			0x0a, // ack_delay_exponent
+			1,    // length
+			4,    // varint value
+		},
+	}, {
+		params: func(p *transportParameters) {
+			p.maxAckDelay = 10 * time.Millisecond
+		},
+		enc: []byte{
+			0x0b, // max_ack_delay
+			1,    // length
+			10,   // varint value
+		},
+	}, {
+		params: func(p *transportParameters) {
+			p.disableActiveMigration = true
+		},
+		enc: []byte{
+			0x0c, // disable_active_migration
+			0,    // length
+		},
+	}, {
+		params: func(p *transportParameters) {
+			p.preferredAddrV4 = netip.MustParseAddrPort("127.0.0.1:80")
+			p.preferredAddrV6 = netip.MustParseAddrPort("[fe80::1]:1024")
+			p.preferredAddrConnID = []byte("connid")
+			p.preferredAddrResetToken = []byte("0123456789abcdef")
+		},
+		enc: []byte{
+			0x0d, // preferred_address
+			byte(4 + 2 + 16 + 2 + 1 + len("connid") + 16), // length
+			127, 0, 0, 1, // v4 address
+			0, 80, // v4 port
+			0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // v6 address
+			0x04, 0x00, // v6 port,
+			6,                            // connection id length
+			'c', 'o', 'n', 'n', 'i', 'd', // connection id
+			'0', '1', '2', '3', '4', '5', '6', '7',
+			'8', '9', 'a', 'b', 'c', 'd', 'e', 'f', // reset token
+		},
+	}, {
+		params: func(p *transportParameters) {
+			p.activeConnIDLimit = 10
+		},
+		enc: []byte{
+			0x0e, // active_connection_id_limit
+			1,    // length
+			10,   // varint value
+		},
+	}, {
+		params: func(p *transportParameters) {
+			p.initialSrcConnID = []byte("connid")
+		},
+		enc: []byte{
+			0x0f, // initial_source_connection_id
+			byte(len("connid")),
+			'c', 'o', 'n', 'n', 'i', 'd',
+		},
+	}, {
+		params: func(p *transportParameters) {
+			p.retrySrcConnID = []byte("connid")
+		},
+		enc: []byte{
+			0x10, // retry_source_connection_id
+			byte(len("connid")),
+			'c', 'o', 'n', 'n', 'i', 'd',
+		},
+	}} {
+		wantParams := defaultTransportParameters()
+		test.params(&wantParams)
+		gotBytes := marshalTransportParameters(wantParams)
+		if !bytes.Equal(gotBytes, test.enc) {
+			t.Errorf("marshalTransportParameters(%#v):\n got: %x\nwant: %x", wantParams, gotBytes, test.enc)
+		}
+		gotParams, err := unmarshalTransportParams(test.enc)
+		if err != nil {
+			t.Errorf("unmarshalTransportParams(%x): unexpected error: %v", test.enc, err)
+		} else if !reflect.DeepEqual(gotParams, wantParams) {
+			t.Errorf("unmarshalTransportParams(%x):\n got: %#v\nwant: %#v", test.enc, gotParams, wantParams)
+		}
+	}
+}
+
+func TestTransportParametersErrors(t *testing.T) {
+	for _, test := range []struct {
+		desc string
+		enc  []byte
+	}{{
+		desc: "invalid id",
+		enc: []byte{
+			0x40, // too short
+		},
+	}, {
+		desc: "parameter too short",
+		enc: []byte{
+			0x00,    // original_destination_connection_id
+			0x04,    // length
+			1, 2, 3, // not enough data
+		},
+	}, {
+		desc: "extra data in parameter",
+		enc: []byte{
+			0x01, // max_idle_timeout
+			2,    // length
+			10,   // varint msecs
+			0,    // extra junk
+		},
+	}, {
+		desc: "invalid varint in parameter",
+		enc: []byte{
+			0x01, // max_idle_timeout
+			1,    // length
+			0x40, // incomplete varint
+		},
+	}, {
+		desc: "stateless_reset_token not 16 bytes",
+		enc: []byte{
+			0x02, // stateless_reset_token,
+			15,   // length
+			0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+		},
+	}, {
+		desc: "preferred_address is too short",
+		enc: []byte{
+			0x0d, // preferred_address
+			byte(3),
+			127, 0, 0,
+		},
+	}, {
+		desc: "preferred_address reset token too short",
+		enc: []byte{
+			0x0d, // preferred_address
+			byte(4 + 2 + 16 + 2 + 1 + len("connid") + 15), // length
+			127, 0, 0, 1, // v4 address
+			0, 80, // v4 port
+			0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // v6 address
+			0x04, 0x00, // v6 port,
+			6,                            // connection id length
+			'c', 'o', 'n', 'n', 'i', 'd', // connection id
+			'0', '1', '2', '3', '4', '5', '6', '7',
+			'8', '9', 'a', 'b', 'c', 'd', 'e', // reset token, one byte too short
+
+		},
+	}, {
+		desc: "preferred_address conn id too long",
+		enc: []byte{
+			0x0d, // preferred_address
+			byte(4 + 2 + 16 + 2 + 1 + len("connid") + 16), // length
+			127, 0, 0, 1, // v4 address
+			0, 80, // v4 port
+			0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // v6 address
+			0x04, 0x00, // v6 port,
+			byte(len("connid")) + 16 + 1, // connection id length, too long
+			'c', 'o', 'n', 'n', 'i', 'd', // connection id
+			'0', '1', '2', '3', '4', '5', '6', '7',
+			'8', '9', 'a', 'b', 'c', 'd', 'e', 'f', // reset token
+
+		},
+	}} {
+		_, err := unmarshalTransportParams(test.enc)
+		if err == nil {
+			t.Errorf("%v:\nunmarshalTransportParams(%x): unexpectedly succeeded", test.desc, test.enc)
+		}
+	}
+}
+
+func TestTransportParametersRangeErrors(t *testing.T) {
+	for _, test := range []struct {
+		desc   string
+		params func(p *transportParameters)
+	}{{
+		desc: "max_udp_payload_size < 1200",
+		params: func(p *transportParameters) {
+			p.maxUDPPayloadSize = 1199
+		},
+	}, {
+		desc: "ack_delay_exponent > 20",
+		params: func(p *transportParameters) {
+			p.ackDelayExponent = 21
+		},
+	}, {
+		desc: "max_ack_delay > 1^14 ms",
+		params: func(p *transportParameters) {
+			p.maxAckDelay = (1 << 14) * time.Millisecond
+		},
+	}, {
+		desc: "active_connection_id_limit < 2",
+		params: func(p *transportParameters) {
+			p.activeConnIDLimit = 1
+		},
+	}} {
+		p := defaultTransportParameters()
+		test.params(&p)
+		enc := marshalTransportParameters(p)
+		_, err := unmarshalTransportParams(enc)
+		if err == nil {
+			t.Errorf("%v: unmarshalTransportParams unexpectedly succeeded", test.desc)
+		}
+	}
+}
+
+func TestTransportParameterMaxIdleTimeoutOverflowsDuration(t *testing.T) {
+	tooManyMS := 1 + (math.MaxInt64 / uint64(time.Millisecond))
+
+	var enc []byte
+	enc = appendVarint(enc, paramMaxIdleTimeout)
+	enc = appendVarint(enc, uint64(sizeVarint(tooManyMS)))
+	enc = appendVarint(enc, uint64(tooManyMS))
+
+	dec, err := unmarshalTransportParams(enc)
+	if err != nil {
+		t.Fatalf("unmarshalTransportParameters(enc) = %v", err)
+	}
+	if got, want := dec.maxIdleTimeout, time.Duration(0); got != want {
+		t.Errorf("max_idle_timeout=%v, got maxIdleTimeout=%v; want %v", tooManyMS, got, want)
+	}
+}
+
+func TestTransportParametersSkipUnknownParameters(t *testing.T) {
+	enc := []byte{
+		0x20, // unknown transport parameter
+		1,    // length
+		0,    // varint value
+
+		0x04, // initial_max_data
+		1,    // length
+		10,   // varint value
+
+		0x21, // unknown transport parameter
+		1,    // length
+		0,    // varint value
+	}
+	dec, err := unmarshalTransportParams(enc)
+	if err != nil {
+		t.Fatalf("unmarshalTransportParameters(enc) = %v", err)
+	}
+	if got, want := dec.initialMaxData, int64(10); got != want {
+		t.Errorf("got initial_max_data=%v; want %v", got, want)
+	}
+}
+
+func FuzzTransportParametersMarshalUnmarshal(f *testing.F) {
+	f.Fuzz(func(t *testing.T, in []byte) {
+		p1, err := unmarshalTransportParams(in)
+		if err != nil {
+			return
+		}
+		out := marshalTransportParameters(p1)
+		p2, err := unmarshalTransportParams(out)
+		if err != nil {
+			t.Fatalf("round trip unmarshal/remarshal: unmarshal error: %v\n%x", err, in)
+		}
+		if !reflect.DeepEqual(p1, p2) {
+			t.Fatalf("round trip unmarshal/remarshal: parameters differ:\n%x\n%#v\n%#v", in, p1, p2)
+		}
+	})
+}