// 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) } }) }