blob: cc88e83fd6b217dfdf8de1dae50148e1b3fc1771 [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 (
"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: "initial_max_streams_bidi is too large",
enc: []byte{
0x08, // initial_max_streams_bidi,
8, // length,
0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
},
}, {
desc: "initial_max_streams_uni is too large",
enc: []byte{
0x08, // initial_max_streams_uni,
9, // length,
0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
},
}, {
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)
}
})
}