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