package wire

import (
	"bytes"
	"fmt"
	"math"
	"net/netip"
	"testing"
	"time"

	"golang.org/x/exp/rand"

	"github.com/quic-go/quic-go/internal/protocol"
	"github.com/quic-go/quic-go/internal/qerr"
	"github.com/quic-go/quic-go/quicvarint"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

func getRandomValueUpTo(max int64) uint64 {
	maxVals := []int64{math.MaxUint8 / 4, math.MaxUint16 / 4, math.MaxUint32 / 4, math.MaxUint64 / 4}
	m := maxVals[int(rand.Int31n(4))]
	if m > max {
		m = max
	}
	return uint64(rand.Int63n(m))
}

func getRandomValue() uint64 {
	return getRandomValueUpTo(quicvarint.Max)
}

var _ = Describe("Transport Parameters", func() {
	BeforeEach(func() {
		rand.Seed(uint64(GinkgoRandomSeed()))
	})

	appendInitialSourceConnectionID := func(b []byte) []byte {
		b = quicvarint.Append(b, uint64(initialSourceConnectionIDParameterID))
		b = quicvarint.Append(b, 6)
		return append(b, []byte("foobar")...)
	}

	It("has a string representation", func() {
		rcid := protocol.ParseConnectionID([]byte{0xde, 0xad, 0xc0, 0xde})
		p := &TransportParameters{
			InitialMaxStreamDataBidiLocal:   1234,
			InitialMaxStreamDataBidiRemote:  2345,
			InitialMaxStreamDataUni:         3456,
			InitialMaxData:                  4567,
			MaxBidiStreamNum:                1337,
			MaxUniStreamNum:                 7331,
			MaxIdleTimeout:                  42 * time.Second,
			OriginalDestinationConnectionID: protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
			InitialSourceConnectionID:       protocol.ParseConnectionID([]byte{0xde, 0xca, 0xfb, 0xad}),
			RetrySourceConnectionID:         &rcid,
			AckDelayExponent:                14,
			MaxAckDelay:                     37 * time.Millisecond,
			StatelessResetToken:             &protocol.StatelessResetToken{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00},
			ActiveConnectionIDLimit:         123,
			MaxDatagramFrameSize:            876,
		}
		Expect(p.String()).To(Equal("&wire.TransportParameters{OriginalDestinationConnectionID: deadbeef, InitialSourceConnectionID: decafbad, RetrySourceConnectionID: deadc0de, InitialMaxStreamDataBidiLocal: 1234, InitialMaxStreamDataBidiRemote: 2345, InitialMaxStreamDataUni: 3456, InitialMaxData: 4567, MaxBidiStreamNum: 1337, MaxUniStreamNum: 7331, MaxIdleTimeout: 42s, AckDelayExponent: 14, MaxAckDelay: 37ms, ActiveConnectionIDLimit: 123, StatelessResetToken: 0x112233445566778899aabbccddeeff00, MaxDatagramFrameSize: 876}"))
	})

	It("has a string representation, if there's no stateless reset token, no Retry source connection id and no datagram support", func() {
		p := &TransportParameters{
			InitialMaxStreamDataBidiLocal:   1234,
			InitialMaxStreamDataBidiRemote:  2345,
			InitialMaxStreamDataUni:         3456,
			InitialMaxData:                  4567,
			MaxBidiStreamNum:                1337,
			MaxUniStreamNum:                 7331,
			MaxIdleTimeout:                  42 * time.Second,
			OriginalDestinationConnectionID: protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
			InitialSourceConnectionID:       protocol.ParseConnectionID([]byte{}),
			AckDelayExponent:                14,
			MaxAckDelay:                     37 * time.Second,
			ActiveConnectionIDLimit:         89,
			MaxDatagramFrameSize:            protocol.InvalidByteCount,
		}
		Expect(p.String()).To(Equal("&wire.TransportParameters{OriginalDestinationConnectionID: deadbeef, InitialSourceConnectionID: (empty), InitialMaxStreamDataBidiLocal: 1234, InitialMaxStreamDataBidiRemote: 2345, InitialMaxStreamDataUni: 3456, InitialMaxData: 4567, MaxBidiStreamNum: 1337, MaxUniStreamNum: 7331, MaxIdleTimeout: 42s, AckDelayExponent: 14, MaxAckDelay: 37s, ActiveConnectionIDLimit: 89}"))
	})

	It("marshals and unmarshals", func() {
		var token protocol.StatelessResetToken
		rand.Read(token[:])
		rcid := protocol.ParseConnectionID([]byte{0xde, 0xad, 0xc0, 0xde})
		params := &TransportParameters{
			InitialMaxStreamDataBidiLocal:   protocol.ByteCount(getRandomValue()),
			InitialMaxStreamDataBidiRemote:  protocol.ByteCount(getRandomValue()),
			InitialMaxStreamDataUni:         protocol.ByteCount(getRandomValue()),
			InitialMaxData:                  protocol.ByteCount(getRandomValue()),
			MaxIdleTimeout:                  0xcafe * time.Second,
			MaxBidiStreamNum:                protocol.StreamNum(getRandomValueUpTo(int64(protocol.MaxStreamCount))),
			MaxUniStreamNum:                 protocol.StreamNum(getRandomValueUpTo(int64(protocol.MaxStreamCount))),
			DisableActiveMigration:          true,
			StatelessResetToken:             &token,
			OriginalDestinationConnectionID: protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
			InitialSourceConnectionID:       protocol.ParseConnectionID([]byte{0xde, 0xca, 0xfb, 0xad}),
			RetrySourceConnectionID:         &rcid,
			AckDelayExponent:                13,
			MaxAckDelay:                     42 * time.Millisecond,
			ActiveConnectionIDLimit:         2 + getRandomValueUpTo(quicvarint.Max-2),
			MaxUDPPayloadSize:               1200 + protocol.ByteCount(getRandomValueUpTo(quicvarint.Max-1200)),
			MaxDatagramFrameSize:            protocol.ByteCount(getRandomValue()),
		}
		data := params.Marshal(protocol.PerspectiveServer)

		p := &TransportParameters{}
		Expect(p.Unmarshal(data, protocol.PerspectiveServer)).To(Succeed())
		Expect(p.InitialMaxStreamDataBidiLocal).To(Equal(params.InitialMaxStreamDataBidiLocal))
		Expect(p.InitialMaxStreamDataBidiRemote).To(Equal(params.InitialMaxStreamDataBidiRemote))
		Expect(p.InitialMaxStreamDataUni).To(Equal(params.InitialMaxStreamDataUni))
		Expect(p.InitialMaxData).To(Equal(params.InitialMaxData))
		Expect(p.MaxUniStreamNum).To(Equal(params.MaxUniStreamNum))
		Expect(p.MaxBidiStreamNum).To(Equal(params.MaxBidiStreamNum))
		Expect(p.MaxIdleTimeout).To(Equal(params.MaxIdleTimeout))
		Expect(p.DisableActiveMigration).To(Equal(params.DisableActiveMigration))
		Expect(p.StatelessResetToken).To(Equal(params.StatelessResetToken))
		Expect(p.OriginalDestinationConnectionID).To(Equal(protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef})))
		Expect(p.InitialSourceConnectionID).To(Equal(protocol.ParseConnectionID([]byte{0xde, 0xca, 0xfb, 0xad})))
		Expect(p.RetrySourceConnectionID).To(Equal(&rcid))
		Expect(p.AckDelayExponent).To(Equal(uint8(13)))
		Expect(p.MaxAckDelay).To(Equal(42 * time.Millisecond))
		Expect(p.ActiveConnectionIDLimit).To(Equal(params.ActiveConnectionIDLimit))
		Expect(p.MaxUDPPayloadSize).To(Equal(params.MaxUDPPayloadSize))
		Expect(p.MaxDatagramFrameSize).To(Equal(params.MaxDatagramFrameSize))
	})

	It("marshals additional transport parameters (used for testing large ClientHellos)", func() {
		origAdditionalTransportParametersClient := AdditionalTransportParametersClient
		defer func() {
			AdditionalTransportParametersClient = origAdditionalTransportParametersClient
		}()
		AdditionalTransportParametersClient = map[uint64][]byte{1337: []byte("foobar")}

		result := quicvarint.Append([]byte{}, 1337)
		result = quicvarint.Append(result, 6)
		result = append(result, []byte("foobar")...)

		params := &TransportParameters{}
		Expect(bytes.Contains(params.Marshal(protocol.PerspectiveClient), result)).To(BeTrue())
		Expect(bytes.Contains(params.Marshal(protocol.PerspectiveServer), result)).To(BeFalse())
	})

	It("doesn't marshal a retry_source_connection_id, if no Retry was performed", func() {
		data := (&TransportParameters{
			StatelessResetToken:     &protocol.StatelessResetToken{},
			ActiveConnectionIDLimit: 2,
		}).Marshal(protocol.PerspectiveServer)
		p := &TransportParameters{}
		Expect(p.Unmarshal(data, protocol.PerspectiveServer)).To(Succeed())
		Expect(p.RetrySourceConnectionID).To(BeNil())
	})

	It("marshals a zero-length retry_source_connection_id", func() {
		rcid := protocol.ParseConnectionID([]byte{})
		data := (&TransportParameters{
			RetrySourceConnectionID: &rcid,
			StatelessResetToken:     &protocol.StatelessResetToken{},
			ActiveConnectionIDLimit: 2,
		}).Marshal(protocol.PerspectiveServer)
		p := &TransportParameters{}
		Expect(p.Unmarshal(data, protocol.PerspectiveServer)).To(Succeed())
		Expect(p.RetrySourceConnectionID).ToNot(BeNil())
		Expect(p.RetrySourceConnectionID.Len()).To(BeZero())
	})

	It("errors when the stateless_reset_token has the wrong length", func() {
		b := quicvarint.Append(nil, uint64(statelessResetTokenParameterID))
		b = quicvarint.Append(b, 15)
		b = append(b, make([]byte, 15)...)
		Expect((&TransportParameters{}).Unmarshal(b, protocol.PerspectiveServer)).To(MatchError(&qerr.TransportError{
			ErrorCode:    qerr.TransportParameterError,
			ErrorMessage: "wrong length for stateless_reset_token: 15 (expected 16)",
		}))
	})

	It("errors when the max_udp_payload_size is too small", func() {
		b := quicvarint.Append(nil, uint64(maxUDPPayloadSizeParameterID))
		b = quicvarint.Append(b, uint64(quicvarint.Len(1199)))
		b = quicvarint.Append(b, 1199)
		Expect((&TransportParameters{}).Unmarshal(b, protocol.PerspectiveServer)).To(MatchError(&qerr.TransportError{
			ErrorCode:    qerr.TransportParameterError,
			ErrorMessage: "invalid value for max_udp_payload_size: 1199 (minimum 1200)",
		}))
	})

	It("errors when disable_active_migration has content", func() {
		b := quicvarint.Append(nil, uint64(disableActiveMigrationParameterID))
		b = quicvarint.Append(b, 6)
		b = append(b, []byte("foobar")...)
		Expect((&TransportParameters{}).Unmarshal(b, protocol.PerspectiveServer)).To(MatchError(&qerr.TransportError{
			ErrorCode:    qerr.TransportParameterError,
			ErrorMessage: "wrong length for disable_active_migration: 6 (expected empty)",
		}))
	})

	It("errors when the server doesn't set the original_destination_connection_id", func() {
		b := quicvarint.Append(nil, uint64(statelessResetTokenParameterID))
		b = quicvarint.Append(b, 16)
		b = append(b, make([]byte, 16)...)
		b = appendInitialSourceConnectionID(b)
		Expect((&TransportParameters{}).Unmarshal(b, protocol.PerspectiveServer)).To(MatchError(&qerr.TransportError{
			ErrorCode:    qerr.TransportParameterError,
			ErrorMessage: "missing original_destination_connection_id",
		}))
	})

	It("errors when the initial_source_connection_id is missing", func() {
		Expect((&TransportParameters{}).Unmarshal([]byte{}, protocol.PerspectiveClient)).To(MatchError(&qerr.TransportError{
			ErrorCode:    qerr.TransportParameterError,
			ErrorMessage: "missing initial_source_connection_id",
		}))
	})

	It("errors when the max_ack_delay is too large", func() {
		data := (&TransportParameters{
			MaxAckDelay:         1 << 14 * time.Millisecond,
			StatelessResetToken: &protocol.StatelessResetToken{},
		}).Marshal(protocol.PerspectiveServer)
		p := &TransportParameters{}
		Expect(p.Unmarshal(data, protocol.PerspectiveServer)).To(MatchError(&qerr.TransportError{
			ErrorCode:    qerr.TransportParameterError,
			ErrorMessage: "invalid value for max_ack_delay: 16384ms (maximum 16383ms)",
		}))
	})

	It("doesn't send the max_ack_delay, if it has the default value", func() {
		const num = 1000
		var defaultLen, dataLen int
		// marshal 1000 times to average out the greasing transport parameter
		maxAckDelay := protocol.DefaultMaxAckDelay + time.Millisecond
		for i := 0; i < num; i++ {
			dataDefault := (&TransportParameters{
				MaxAckDelay:         protocol.DefaultMaxAckDelay,
				StatelessResetToken: &protocol.StatelessResetToken{},
			}).Marshal(protocol.PerspectiveServer)
			defaultLen += len(dataDefault)
			data := (&TransportParameters{
				MaxAckDelay:         maxAckDelay,
				StatelessResetToken: &protocol.StatelessResetToken{},
			}).Marshal(protocol.PerspectiveServer)
			dataLen += len(data)
		}
		entryLen := quicvarint.Len(uint64(ackDelayExponentParameterID)) /* parameter id */ + quicvarint.Len(uint64(quicvarint.Len(uint64(maxAckDelay.Milliseconds())))) /*length */ + quicvarint.Len(uint64(maxAckDelay.Milliseconds())) /* value */
		Expect(float32(dataLen) / num).To(BeNumerically("~", float32(defaultLen)/num+float32(entryLen), 1))
	})

	It("errors when the active_connection_id_limit is too small", func() {
		data := (&TransportParameters{
			ActiveConnectionIDLimit: 1,
			StatelessResetToken:     &protocol.StatelessResetToken{},
		}).Marshal(protocol.PerspectiveServer)
		p := &TransportParameters{}
		Expect(p.Unmarshal(data, protocol.PerspectiveServer)).To(MatchError(&qerr.TransportError{
			ErrorCode:    qerr.TransportParameterError,
			ErrorMessage: "invalid value for active_connection_id_limit: 1 (minimum 2)",
		}))
	})

	It("errors when the ack_delay_exponenent is too large", func() {
		data := (&TransportParameters{
			AckDelayExponent:    21,
			StatelessResetToken: &protocol.StatelessResetToken{},
		}).Marshal(protocol.PerspectiveServer)
		p := &TransportParameters{}
		Expect(p.Unmarshal(data, protocol.PerspectiveServer)).To(MatchError(&qerr.TransportError{
			ErrorCode:    qerr.TransportParameterError,
			ErrorMessage: "invalid value for ack_delay_exponent: 21 (maximum 20)",
		}))
	})

	It("doesn't send the ack_delay_exponent, if it has the default value", func() {
		const num = 1000
		var defaultLen, dataLen int
		// marshal 1000 times to average out the greasing transport parameter
		for i := 0; i < num; i++ {
			dataDefault := (&TransportParameters{
				AckDelayExponent:    protocol.DefaultAckDelayExponent,
				StatelessResetToken: &protocol.StatelessResetToken{},
			}).Marshal(protocol.PerspectiveServer)
			defaultLen += len(dataDefault)
			data := (&TransportParameters{
				AckDelayExponent:    protocol.DefaultAckDelayExponent + 1,
				StatelessResetToken: &protocol.StatelessResetToken{},
			}).Marshal(protocol.PerspectiveServer)
			dataLen += len(data)
		}
		entryLen := quicvarint.Len(uint64(ackDelayExponentParameterID)) /* parameter id */ + quicvarint.Len(uint64(quicvarint.Len(protocol.DefaultAckDelayExponent+1))) /* length */ + quicvarint.Len(protocol.DefaultAckDelayExponent+1) /* value */
		Expect(float32(dataLen) / num).To(BeNumerically("~", float32(defaultLen)/num+float32(entryLen), 1))
	})

	It("sets the default value for the ack_delay_exponent and max_active_connection_id_limit, when no values were sent", func() {
		data := (&TransportParameters{
			AckDelayExponent:        protocol.DefaultAckDelayExponent,
			StatelessResetToken:     &protocol.StatelessResetToken{},
			ActiveConnectionIDLimit: protocol.DefaultActiveConnectionIDLimit,
		}).Marshal(protocol.PerspectiveServer)
		p := &TransportParameters{}
		Expect(p.Unmarshal(data, protocol.PerspectiveServer)).To(Succeed())
		Expect(p.AckDelayExponent).To(BeEquivalentTo(protocol.DefaultAckDelayExponent))
		Expect(p.ActiveConnectionIDLimit).To(BeEquivalentTo(protocol.DefaultActiveConnectionIDLimit))
	})

	It("errors when the varint value has the wrong length", func() {
		b := quicvarint.Append(nil, uint64(initialMaxStreamDataBidiLocalParameterID))
		b = quicvarint.Append(b, 2)
		val := uint64(0xdeadbeef)
		Expect(quicvarint.Len(val)).ToNot(BeEquivalentTo(2))
		b = quicvarint.Append(b, val)
		b = appendInitialSourceConnectionID(b)
		Expect((&TransportParameters{}).Unmarshal(b, protocol.PerspectiveServer)).To(MatchError(&qerr.TransportError{
			ErrorCode:    qerr.TransportParameterError,
			ErrorMessage: fmt.Sprintf("inconsistent transport parameter length for transport parameter %#x", initialMaxStreamDataBidiLocalParameterID),
		}))
	})

	It("errors if initial_max_streams_bidi is too large", func() {
		b := quicvarint.Append(nil, uint64(initialMaxStreamsBidiParameterID))
		b = quicvarint.Append(b, uint64(quicvarint.Len(uint64(protocol.MaxStreamCount+1))))
		b = quicvarint.Append(b, uint64(protocol.MaxStreamCount+1))
		b = appendInitialSourceConnectionID(b)
		Expect((&TransportParameters{}).Unmarshal(b, protocol.PerspectiveServer)).To(MatchError(&qerr.TransportError{
			ErrorCode:    qerr.TransportParameterError,
			ErrorMessage: "initial_max_streams_bidi too large: 1152921504606846977 (maximum 1152921504606846976)",
		}))
	})

	It("errors if initial_max_streams_uni is too large", func() {
		b := quicvarint.Append(nil, uint64(initialMaxStreamsUniParameterID))
		b = quicvarint.Append(b, uint64(quicvarint.Len(uint64(protocol.MaxStreamCount+1))))
		b = quicvarint.Append(b, uint64(protocol.MaxStreamCount+1))
		b = appendInitialSourceConnectionID(b)
		Expect((&TransportParameters{}).Unmarshal(b, protocol.PerspectiveServer)).To(MatchError(&qerr.TransportError{
			ErrorCode:    qerr.TransportParameterError,
			ErrorMessage: "initial_max_streams_uni too large: 1152921504606846977 (maximum 1152921504606846976)",
		}))
	})

	It("handles huge max_ack_delay values", func() {
		val := uint64(math.MaxUint64) / 5
		b := quicvarint.Append(nil, uint64(maxAckDelayParameterID))
		b = quicvarint.Append(b, uint64(quicvarint.Len(val)))
		b = quicvarint.Append(b, val)
		b = appendInitialSourceConnectionID(b)
		Expect((&TransportParameters{}).Unmarshal(b, protocol.PerspectiveClient)).To(MatchError(&qerr.TransportError{
			ErrorCode:    qerr.TransportParameterError,
			ErrorMessage: "invalid value for max_ack_delay: 3689348814741910323ms (maximum 16383ms)",
		}))
	})

	It("skips unknown parameters", func() {
		// write a known parameter
		b := quicvarint.Append(nil, uint64(initialMaxStreamDataBidiLocalParameterID))
		b = quicvarint.Append(b, uint64(quicvarint.Len(0x1337)))
		b = quicvarint.Append(b, 0x1337)
		// write an unknown parameter
		b = quicvarint.Append(b, 0x42)
		b = quicvarint.Append(b, 6)
		b = append(b, []byte("foobar")...)
		// write a known parameter
		b = quicvarint.Append(b, uint64(initialMaxStreamDataBidiRemoteParameterID))
		b = quicvarint.Append(b, uint64(quicvarint.Len(0x42)))
		b = quicvarint.Append(b, 0x42)
		b = appendInitialSourceConnectionID(b)
		p := &TransportParameters{}
		Expect(p.Unmarshal(b, protocol.PerspectiveClient)).To(Succeed())
		Expect(p.InitialMaxStreamDataBidiLocal).To(Equal(protocol.ByteCount(0x1337)))
		Expect(p.InitialMaxStreamDataBidiRemote).To(Equal(protocol.ByteCount(0x42)))
	})

	It("rejects duplicate parameters", func() {
		// write first parameter
		b := quicvarint.Append(nil, uint64(initialMaxStreamDataBidiLocalParameterID))
		b = quicvarint.Append(b, uint64(quicvarint.Len(0x1337)))
		b = quicvarint.Append(b, 0x1337)
		// write a second parameter
		b = quicvarint.Append(b, uint64(initialMaxStreamDataBidiRemoteParameterID))
		b = quicvarint.Append(b, uint64(quicvarint.Len(0x42)))
		b = quicvarint.Append(b, 0x42)
		// write first parameter again
		b = quicvarint.Append(b, uint64(initialMaxStreamDataBidiLocalParameterID))
		b = quicvarint.Append(b, uint64(quicvarint.Len(0x1337)))
		b = quicvarint.Append(b, 0x1337)
		b = appendInitialSourceConnectionID(b)
		Expect((&TransportParameters{}).Unmarshal(b, protocol.PerspectiveClient)).To(MatchError(&qerr.TransportError{
			ErrorCode:    qerr.TransportParameterError,
			ErrorMessage: fmt.Sprintf("received duplicate transport parameter %#x", initialMaxStreamDataBidiLocalParameterID),
		}))
	})

	It("errors if there's not enough data to read", func() {
		b := quicvarint.Append(nil, 0x42)
		b = quicvarint.Append(b, 7)
		b = append(b, []byte("foobar")...)
		p := &TransportParameters{}
		Expect(p.Unmarshal(b, protocol.PerspectiveServer)).To(MatchError(&qerr.TransportError{
			ErrorCode:    qerr.TransportParameterError,
			ErrorMessage: "remaining length (6) smaller than parameter length (7)",
		}))
	})

	It("errors if the client sent a stateless_reset_token", func() {
		b := quicvarint.Append(nil, uint64(statelessResetTokenParameterID))
		b = quicvarint.Append(b, uint64(quicvarint.Len(16)))
		b = append(b, make([]byte, 16)...)
		Expect((&TransportParameters{}).Unmarshal(b, protocol.PerspectiveClient)).To(MatchError(&qerr.TransportError{
			ErrorCode:    qerr.TransportParameterError,
			ErrorMessage: "client sent a stateless_reset_token",
		}))
	})

	It("errors if the client sent the original_destination_connection_id", func() {
		b := quicvarint.Append(nil, uint64(originalDestinationConnectionIDParameterID))
		b = quicvarint.Append(b, 6)
		b = append(b, []byte("foobar")...)
		Expect((&TransportParameters{}).Unmarshal(b, protocol.PerspectiveClient)).To(MatchError(&qerr.TransportError{
			ErrorCode:    qerr.TransportParameterError,
			ErrorMessage: "client sent an original_destination_connection_id",
		}))
	})

	Context("preferred address", func() {
		var pa *PreferredAddress

		BeforeEach(func() {
			pa = &PreferredAddress{
				IPv4:                netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), 42),
				IPv6:                netip.AddrPortFrom(netip.AddrFrom16([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), 13),
				ConnectionID:        protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
				StatelessResetToken: protocol.StatelessResetToken{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
			}
		})

		It("marshals and unmarshals", func() {
			data := (&TransportParameters{
				PreferredAddress:        pa,
				StatelessResetToken:     &protocol.StatelessResetToken{},
				ActiveConnectionIDLimit: 2,
			}).Marshal(protocol.PerspectiveServer)
			p := &TransportParameters{}
			Expect(p.Unmarshal(data, protocol.PerspectiveServer)).To(Succeed())
			Expect(p.PreferredAddress.IPv4).To(Equal(pa.IPv4))
			Expect(p.PreferredAddress.IPv6).To(Equal(pa.IPv6))
			Expect(p.PreferredAddress.ConnectionID).To(Equal(pa.ConnectionID))
			Expect(p.PreferredAddress.StatelessResetToken).To(Equal(pa.StatelessResetToken))
		})

		It("errors if the client sent a preferred_address", func() {
			b := quicvarint.Append(nil, uint64(preferredAddressParameterID))
			b = quicvarint.Append(b, 6)
			b = append(b, []byte("foobar")...)
			p := &TransportParameters{}
			Expect(p.Unmarshal(b, protocol.PerspectiveClient)).To(MatchError(&qerr.TransportError{
				ErrorCode:    qerr.TransportParameterError,
				ErrorMessage: "client sent a preferred_address",
			}))
		})

		It("errors on zero-length connection IDs", func() {
			pa.ConnectionID = protocol.ParseConnectionID([]byte{})
			data := (&TransportParameters{
				PreferredAddress:    pa,
				StatelessResetToken: &protocol.StatelessResetToken{},
			}).Marshal(protocol.PerspectiveServer)
			p := &TransportParameters{}
			Expect(p.Unmarshal(data, protocol.PerspectiveServer)).To(MatchError(&qerr.TransportError{
				ErrorCode:    qerr.TransportParameterError,
				ErrorMessage: "invalid connection ID length: 0",
			}))
		})

		It("errors on EOF", func() {
			raw := []byte{
				127, 0, 0, 1, // IPv4
				0, 42, // IPv4 Port
				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // IPv6
				13, 37, // IPv6 Port,
				4, // conn ID len
				0xde, 0xad, 0xbe, 0xef,
				16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, // stateless reset token
			}
			for i := 1; i < len(raw); i++ {
				b := quicvarint.Append(nil, uint64(preferredAddressParameterID))
				b = append(b, raw[:i]...)
				p := &TransportParameters{}
				Expect(p.Unmarshal(b, protocol.PerspectiveServer)).ToNot(Succeed())
			}
		})
	})

	Context("saving and retrieving from a session ticket", func() {
		It("saves and retrieves the parameters", func() {
			params := &TransportParameters{
				InitialMaxStreamDataBidiLocal:  protocol.ByteCount(getRandomValue()),
				InitialMaxStreamDataBidiRemote: protocol.ByteCount(getRandomValue()),
				InitialMaxStreamDataUni:        protocol.ByteCount(getRandomValue()),
				InitialMaxData:                 protocol.ByteCount(getRandomValue()),
				MaxBidiStreamNum:               protocol.StreamNum(getRandomValueUpTo(int64(protocol.MaxStreamCount))),
				MaxUniStreamNum:                protocol.StreamNum(getRandomValueUpTo(int64(protocol.MaxStreamCount))),
				ActiveConnectionIDLimit:        2 + getRandomValueUpTo(quicvarint.Max-2),
				MaxDatagramFrameSize:           protocol.ByteCount(getRandomValueUpTo(int64(MaxDatagramSize))),
			}
			Expect(params.ValidFor0RTT(params)).To(BeTrue())
			b := params.MarshalForSessionTicket(nil)
			var tp TransportParameters
			Expect(tp.UnmarshalFromSessionTicket(b)).To(Succeed())
			Expect(tp.InitialMaxStreamDataBidiLocal).To(Equal(params.InitialMaxStreamDataBidiLocal))
			Expect(tp.InitialMaxStreamDataBidiRemote).To(Equal(params.InitialMaxStreamDataBidiRemote))
			Expect(tp.InitialMaxStreamDataUni).To(Equal(params.InitialMaxStreamDataUni))
			Expect(tp.InitialMaxData).To(Equal(params.InitialMaxData))
			Expect(tp.MaxBidiStreamNum).To(Equal(params.MaxBidiStreamNum))
			Expect(tp.MaxUniStreamNum).To(Equal(params.MaxUniStreamNum))
			Expect(tp.ActiveConnectionIDLimit).To(Equal(params.ActiveConnectionIDLimit))
			Expect(tp.MaxDatagramFrameSize).To(Equal(params.MaxDatagramFrameSize))
		})

		It("rejects the parameters if it can't parse them", func() {
			var p TransportParameters
			Expect(p.UnmarshalFromSessionTicket([]byte("foobar"))).ToNot(Succeed())
		})

		It("rejects the parameters if the version changed", func() {
			var p TransportParameters
			data := p.MarshalForSessionTicket(nil)
			b := quicvarint.Append(nil, transportParameterMarshalingVersion+1)
			b = append(b, data[quicvarint.Len(transportParameterMarshalingVersion):]...)
			Expect(p.UnmarshalFromSessionTicket(b)).To(MatchError(fmt.Sprintf("unknown transport parameter marshaling version: %d", transportParameterMarshalingVersion+1)))
		})

		Context("rejects the parameters if they changed", func() {
			var p TransportParameters
			saved := &TransportParameters{
				InitialMaxStreamDataBidiLocal:  1,
				InitialMaxStreamDataBidiRemote: 2,
				InitialMaxStreamDataUni:        3,
				InitialMaxData:                 4,
				MaxBidiStreamNum:               5,
				MaxUniStreamNum:                6,
				ActiveConnectionIDLimit:        7,
				MaxDatagramFrameSize:           1000,
			}

			BeforeEach(func() {
				p = *saved
				Expect(p.ValidFor0RTT(saved)).To(BeTrue())
			})

			It("rejects the parameters if the InitialMaxStreamDataBidiLocal was reduced", func() {
				p.InitialMaxStreamDataBidiLocal = saved.InitialMaxStreamDataBidiLocal - 1
				Expect(p.ValidFor0RTT(saved)).To(BeFalse())
			})

			It("doesn't reject the parameters if the InitialMaxStreamDataBidiLocal was increased", func() {
				p.InitialMaxStreamDataBidiLocal = saved.InitialMaxStreamDataBidiLocal + 1
				Expect(p.ValidFor0RTT(saved)).To(BeTrue())
			})

			It("rejects the parameters if the InitialMaxStreamDataBidiRemote was reduced", func() {
				p.InitialMaxStreamDataBidiRemote = saved.InitialMaxStreamDataBidiRemote - 1
				Expect(p.ValidFor0RTT(saved)).To(BeFalse())
			})

			It("doesn't reject the parameters if the InitialMaxStreamDataBidiRemote was increased", func() {
				p.InitialMaxStreamDataBidiRemote = saved.InitialMaxStreamDataBidiRemote + 1
				Expect(p.ValidFor0RTT(saved)).To(BeTrue())
			})

			It("rejects the parameters if the InitialMaxStreamDataUni was reduced", func() {
				p.InitialMaxStreamDataUni = saved.InitialMaxStreamDataUni - 1
				Expect(p.ValidFor0RTT(saved)).To(BeFalse())
			})

			It("doesn't reject the parameters if the InitialMaxStreamDataUni was increased", func() {
				p.InitialMaxStreamDataUni = saved.InitialMaxStreamDataUni + 1
				Expect(p.ValidFor0RTT(saved)).To(BeTrue())
			})

			It("rejects the parameters if the InitialMaxData was reduced", func() {
				p.InitialMaxData = saved.InitialMaxData - 1
				Expect(p.ValidFor0RTT(saved)).To(BeFalse())
			})

			It("doesn't reject the parameters if the InitialMaxData was increased", func() {
				p.InitialMaxData = saved.InitialMaxData + 1
				Expect(p.ValidFor0RTT(saved)).To(BeTrue())
			})

			It("rejects the parameters if the MaxBidiStreamNum was reduced", func() {
				p.MaxBidiStreamNum = saved.MaxBidiStreamNum - 1
				Expect(p.ValidFor0RTT(saved)).To(BeFalse())
			})

			It("accepts the parameters if the MaxBidiStreamNum was increased", func() {
				p.MaxBidiStreamNum = saved.MaxBidiStreamNum + 1
				Expect(p.ValidFor0RTT(saved)).To(BeTrue())
			})

			It("rejects the parameters if the MaxUniStreamNum changed", func() {
				p.MaxUniStreamNum = saved.MaxUniStreamNum - 1
				Expect(p.ValidFor0RTT(saved)).To(BeFalse())
			})

			It("accepts the parameters if the MaxUniStreamNum was increased", func() {
				p.MaxUniStreamNum = saved.MaxUniStreamNum + 1
				Expect(p.ValidFor0RTT(saved)).To(BeTrue())
			})

			It("rejects the parameters if the ActiveConnectionIDLimit changed", func() {
				p.ActiveConnectionIDLimit = 0
				Expect(p.ValidFor0RTT(saved)).To(BeFalse())
			})

			It("accepts the parameters if the MaxDatagramFrameSize was increased", func() {
				p.MaxDatagramFrameSize = saved.MaxDatagramFrameSize + 1
				Expect(p.ValidFor0RTT(saved)).To(BeTrue())
			})

			It("rejects the parameters if the MaxDatagramFrameSize reduced", func() {
				p.MaxDatagramFrameSize = saved.MaxDatagramFrameSize - 1
				Expect(p.ValidFor0RTT(saved)).To(BeFalse())
			})
		})

		Context("client checks the parameters after successfully sending 0-RTT data", func() {
			var p TransportParameters
			saved := &TransportParameters{
				InitialMaxStreamDataBidiLocal:  1,
				InitialMaxStreamDataBidiRemote: 2,
				InitialMaxStreamDataUni:        3,
				InitialMaxData:                 4,
				MaxBidiStreamNum:               5,
				MaxUniStreamNum:                6,
				ActiveConnectionIDLimit:        7,
				MaxDatagramFrameSize:           1000,
			}

			BeforeEach(func() {
				p = *saved
				Expect(p.ValidForUpdate(saved)).To(BeTrue())
			})

			It("rejects the parameters if the InitialMaxStreamDataBidiLocal was reduced", func() {
				p.InitialMaxStreamDataBidiLocal = saved.InitialMaxStreamDataBidiLocal - 1
				Expect(p.ValidForUpdate(saved)).To(BeFalse())
			})

			It("doesn't reject the parameters if the InitialMaxStreamDataBidiLocal was increased", func() {
				p.InitialMaxStreamDataBidiLocal = saved.InitialMaxStreamDataBidiLocal + 1
				Expect(p.ValidForUpdate(saved)).To(BeTrue())
			})

			It("rejects the parameters if the InitialMaxStreamDataBidiRemote was reduced", func() {
				p.InitialMaxStreamDataBidiRemote = saved.InitialMaxStreamDataBidiRemote - 1
				Expect(p.ValidForUpdate(saved)).To(BeFalse())
			})

			It("doesn't reject the parameters if the InitialMaxStreamDataBidiRemote was increased", func() {
				p.InitialMaxStreamDataBidiRemote = saved.InitialMaxStreamDataBidiRemote + 1
				Expect(p.ValidForUpdate(saved)).To(BeTrue())
			})

			It("rejects the parameters if the InitialMaxStreamDataUni was reduced", func() {
				p.InitialMaxStreamDataUni = saved.InitialMaxStreamDataUni - 1
				Expect(p.ValidForUpdate(saved)).To(BeFalse())
			})

			It("doesn't reject the parameters if the InitialMaxStreamDataUni was increased", func() {
				p.InitialMaxStreamDataUni = saved.InitialMaxStreamDataUni + 1
				Expect(p.ValidForUpdate(saved)).To(BeTrue())
			})

			It("rejects the parameters if the InitialMaxData was reduced", func() {
				p.InitialMaxData = saved.InitialMaxData - 1
				Expect(p.ValidForUpdate(saved)).To(BeFalse())
			})

			It("doesn't reject the parameters if the InitialMaxData was increased", func() {
				p.InitialMaxData = saved.InitialMaxData + 1
				Expect(p.ValidForUpdate(saved)).To(BeTrue())
			})

			It("rejects the parameters if the MaxBidiStreamNum was reduced", func() {
				p.MaxBidiStreamNum = saved.MaxBidiStreamNum - 1
				Expect(p.ValidForUpdate(saved)).To(BeFalse())
			})

			It("doesn't reject the parameters if the MaxBidiStreamNum was increased", func() {
				p.MaxBidiStreamNum = saved.MaxBidiStreamNum + 1
				Expect(p.ValidForUpdate(saved)).To(BeTrue())
			})

			It("rejects the parameters if the MaxUniStreamNum reduced", func() {
				p.MaxUniStreamNum = saved.MaxUniStreamNum - 1
				Expect(p.ValidForUpdate(saved)).To(BeFalse())
			})

			It("doesn't reject the parameters if the MaxUniStreamNum was increased", func() {
				p.MaxUniStreamNum = saved.MaxUniStreamNum + 1
				Expect(p.ValidForUpdate(saved)).To(BeTrue())
			})

			It("rejects the parameters if the ActiveConnectionIDLimit reduced", func() {
				p.ActiveConnectionIDLimit = saved.ActiveConnectionIDLimit - 1
				Expect(p.ValidForUpdate(saved)).To(BeFalse())
			})

			It("doesn't reject the parameters if the ActiveConnectionIDLimit increased", func() {
				p.ActiveConnectionIDLimit = saved.ActiveConnectionIDLimit + 1
				Expect(p.ValidForUpdate(saved)).To(BeTrue())
			})

			It("rejects the parameters if the MaxDatagramFrameSize reduced", func() {
				p.MaxDatagramFrameSize = saved.MaxDatagramFrameSize - 1
				Expect(p.ValidForUpdate(saved)).To(BeFalse())
			})

			It("doesn't reject the parameters if the MaxDatagramFrameSize increased", func() {
				p.MaxDatagramFrameSize = saved.MaxDatagramFrameSize + 1
				Expect(p.ValidForUpdate(saved)).To(BeTrue())
			})
		})
	})
})

func BenchmarkTransportParameters(b *testing.B) {
	b.Run("without preferred address", func(b *testing.B) { benchmarkTransportParameters(b, false) })
	b.Run("with preferred address", func(b *testing.B) { benchmarkTransportParameters(b, true) })
}

func benchmarkTransportParameters(b *testing.B, withPreferredAddress bool) {
	var token protocol.StatelessResetToken
	rand.Read(token[:])
	rcid := protocol.ParseConnectionID([]byte{0xde, 0xad, 0xc0, 0xde})
	params := &TransportParameters{
		InitialMaxStreamDataBidiLocal:   protocol.ByteCount(getRandomValue()),
		InitialMaxStreamDataBidiRemote:  protocol.ByteCount(getRandomValue()),
		InitialMaxStreamDataUni:         protocol.ByteCount(getRandomValue()),
		InitialMaxData:                  protocol.ByteCount(getRandomValue()),
		MaxIdleTimeout:                  0xcafe * time.Second,
		MaxBidiStreamNum:                protocol.StreamNum(getRandomValueUpTo(int64(protocol.MaxStreamCount))),
		MaxUniStreamNum:                 protocol.StreamNum(getRandomValueUpTo(int64(protocol.MaxStreamCount))),
		DisableActiveMigration:          true,
		StatelessResetToken:             &token,
		OriginalDestinationConnectionID: protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
		InitialSourceConnectionID:       protocol.ParseConnectionID([]byte{0xde, 0xca, 0xfb, 0xad}),
		RetrySourceConnectionID:         &rcid,
		AckDelayExponent:                13,
		MaxAckDelay:                     42 * time.Millisecond,
		ActiveConnectionIDLimit:         2 + getRandomValueUpTo(quicvarint.Max-2),
		MaxDatagramFrameSize:            protocol.ByteCount(getRandomValue()),
	}
	var token2 protocol.StatelessResetToken
	rand.Read(token2[:])
	if withPreferredAddress {
		var ip4 [4]byte
		var ip6 [16]byte
		rand.Read(ip4[:])
		rand.Read(ip6[:])
		params.PreferredAddress = &PreferredAddress{
			IPv4:                netip.AddrPortFrom(netip.AddrFrom4(ip4), 1234),
			IPv6:                netip.AddrPortFrom(netip.AddrFrom16(ip6), 4321),
			ConnectionID:        protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
			StatelessResetToken: token2,
		}
	}
	data := params.Marshal(protocol.PerspectiveServer)

	b.ResetTimer()
	b.ReportAllocs()
	var p TransportParameters
	for i := 0; i < b.N; i++ {
		if err := p.Unmarshal(data, protocol.PerspectiveServer); err != nil {
			b.Fatal(err)
		}
		// check a few fields
		if p.DisableActiveMigration != params.DisableActiveMigration ||
			p.InitialMaxStreamDataBidiLocal != params.InitialMaxStreamDataBidiLocal ||
			*p.StatelessResetToken != *params.StatelessResetToken ||
			p.AckDelayExponent != params.AckDelayExponent {
			b.Fatalf("params mismatch: %v vs %v", p, params)
		}
		if withPreferredAddress && *p.PreferredAddress != *params.PreferredAddress {
			b.Fatalf("preferred address mismatch: %v vs %v", p.PreferredAddress, params.PreferredAddress)
		}
	}
}
