golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/quic/packet_codec_test.go (about)

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build go1.21
     6  
     7  package quic
     8  
     9  import (
    10  	"bytes"
    11  	"crypto/tls"
    12  	"io"
    13  	"log/slog"
    14  	"reflect"
    15  	"testing"
    16  	"time"
    17  
    18  	"golang.org/x/net/quic/qlog"
    19  )
    20  
    21  func TestParseLongHeaderPacket(t *testing.T) {
    22  	// Example Initial packet from:
    23  	// https://www.rfc-editor.org/rfc/rfc9001.html#section-a.3
    24  	cid := unhex(`8394c8f03e515708`)
    25  	initialServerKeys := initialKeys(cid, clientSide).r
    26  	pkt := unhex(`
    27  		cf000000010008f067a5502a4262b500 4075c0d95a482cd0991cd25b0aac406a
    28  		5816b6394100f37a1c69797554780bb3 8cc5a99f5ede4cf73c3ec2493a1839b3
    29  		dbcba3f6ea46c5b7684df3548e7ddeb9 c3bf9c73cc3f3bded74b562bfb19fb84
    30  		022f8ef4cdd93795d77d06edbb7aaf2f 58891850abbdca3d20398c276456cbc4
    31  		2158407dd074ee
    32  	`)
    33  	want := longPacket{
    34  		ptype:     packetTypeInitial,
    35  		version:   1,
    36  		num:       1,
    37  		dstConnID: []byte{},
    38  		srcConnID: unhex(`f067a5502a4262b5`),
    39  		payload: unhex(`
    40  			02000000000600405a020000560303ee fce7f7b37ba1d1632e96677825ddf739
    41  			88cfc79825df566dc5430b9a045a1200 130100002e00330024001d00209d3c94
    42  			0d89690b84d08a60993c144eca684d10 81287c834d5311bcf32bb9da1a002b00
    43  			020304
    44  		`),
    45  		extra: []byte{},
    46  	}
    47  
    48  	// Parse the packet.
    49  	got, n := parseLongHeaderPacket(pkt, initialServerKeys, 0)
    50  	if n != len(pkt) {
    51  		t.Errorf("parseLongHeaderPacket: n=%v, want %v", n, len(pkt))
    52  	}
    53  	if !reflect.DeepEqual(got, want) {
    54  		t.Errorf("parseLongHeaderPacket:\n got: %+v\nwant: %+v", got, want)
    55  	}
    56  
    57  	// Skip the packet.
    58  	if got, want := skipLongHeaderPacket(pkt), len(pkt); got != want {
    59  		t.Errorf("skipLongHeaderPacket: n=%v, want %v", got, want)
    60  	}
    61  
    62  	// Parse truncated versions of the packet; every attempt should fail.
    63  	for i := 0; i < len(pkt); i++ {
    64  		if _, n := parseLongHeaderPacket(pkt[:i], initialServerKeys, 0); n != -1 {
    65  			t.Fatalf("parse truncated long header packet: n=%v, want -1\ninput: %x", n, pkt[:i])
    66  		}
    67  		if n := skipLongHeaderPacket(pkt[:i]); n != -1 {
    68  			t.Errorf("skip truncated long header packet: n=%v, want -1", n)
    69  		}
    70  	}
    71  
    72  	// Parse with the wrong keys.
    73  	invalidKeys := initialKeys([]byte{}, clientSide).w
    74  	if _, n := parseLongHeaderPacket(pkt, invalidKeys, 0); n != -1 {
    75  		t.Fatalf("parse long header packet with wrong keys: n=%v, want -1", n)
    76  	}
    77  }
    78  
    79  func TestRoundtripEncodeLongPacket(t *testing.T) {
    80  	var aes128Keys, aes256Keys, chachaKeys fixedKeys
    81  	aes128Keys.init(tls.TLS_AES_128_GCM_SHA256, []byte("secret"))
    82  	aes256Keys.init(tls.TLS_AES_256_GCM_SHA384, []byte("secret"))
    83  	chachaKeys.init(tls.TLS_CHACHA20_POLY1305_SHA256, []byte("secret"))
    84  	for _, test := range []struct {
    85  		desc string
    86  		p    longPacket
    87  		k    fixedKeys
    88  	}{{
    89  		desc: "Initial, 1-byte number, AES128",
    90  		p: longPacket{
    91  			ptype:     packetTypeInitial,
    92  			version:   0x11223344,
    93  			num:       0, // 1-byte encodeing
    94  			dstConnID: []byte{1, 2, 3, 4},
    95  			srcConnID: []byte{5, 6, 7, 8},
    96  			payload:   []byte("payload"),
    97  			extra:     []byte("token"),
    98  		},
    99  		k: aes128Keys,
   100  	}, {
   101  		desc: "0-RTT, 2-byte number, AES256",
   102  		p: longPacket{
   103  			ptype:     packetType0RTT,
   104  			version:   0x11223344,
   105  			num:       0x100, // 2-byte encoding
   106  			dstConnID: []byte{1, 2, 3, 4},
   107  			srcConnID: []byte{5, 6, 7, 8},
   108  			payload:   []byte("payload"),
   109  		},
   110  		k: aes256Keys,
   111  	}, {
   112  		desc: "0-RTT, 3-byte number, AES256",
   113  		p: longPacket{
   114  			ptype:     packetType0RTT,
   115  			version:   0x11223344,
   116  			num:       0x10000, // 2-byte encoding
   117  			dstConnID: []byte{1, 2, 3, 4},
   118  			srcConnID: []byte{5, 6, 7, 8},
   119  			payload:   []byte{0},
   120  		},
   121  		k: aes256Keys,
   122  	}, {
   123  		desc: "Handshake, 4-byte number, ChaCha20Poly1305",
   124  		p: longPacket{
   125  			ptype:     packetTypeHandshake,
   126  			version:   0x11223344,
   127  			num:       0x1000000, // 4-byte encoding
   128  			dstConnID: []byte{1, 2, 3, 4},
   129  			srcConnID: []byte{5, 6, 7, 8},
   130  			payload:   []byte("payload"),
   131  		},
   132  		k: chachaKeys,
   133  	}} {
   134  		t.Run(test.desc, func(t *testing.T) {
   135  			var w packetWriter
   136  			w.reset(1200)
   137  			w.startProtectedLongHeaderPacket(0, test.p)
   138  			w.b = append(w.b, test.p.payload...)
   139  			w.finishProtectedLongHeaderPacket(0, test.k, test.p)
   140  			pkt := w.datagram()
   141  
   142  			got, n := parseLongHeaderPacket(pkt, test.k, 0)
   143  			if n != len(pkt) {
   144  				t.Errorf("parseLongHeaderPacket: n=%v, want %v", n, len(pkt))
   145  			}
   146  			if !reflect.DeepEqual(got, test.p) {
   147  				t.Errorf("Round-trip encode/decode did not preserve packet.\nsent: %+v\n got: %+v\nwire: %x", test.p, got, pkt)
   148  			}
   149  		})
   150  	}
   151  }
   152  
   153  func TestRoundtripEncodeShortPacket(t *testing.T) {
   154  	var aes128Keys, aes256Keys, chachaKeys updatingKeyPair
   155  	aes128Keys.r.init(tls.TLS_AES_128_GCM_SHA256, []byte("secret"))
   156  	aes256Keys.r.init(tls.TLS_AES_256_GCM_SHA384, []byte("secret"))
   157  	chachaKeys.r.init(tls.TLS_CHACHA20_POLY1305_SHA256, []byte("secret"))
   158  	aes128Keys.w = aes128Keys.r
   159  	aes256Keys.w = aes256Keys.r
   160  	chachaKeys.w = chachaKeys.r
   161  	aes128Keys.updateAfter = maxPacketNumber
   162  	aes256Keys.updateAfter = maxPacketNumber
   163  	chachaKeys.updateAfter = maxPacketNumber
   164  	connID := make([]byte, connIDLen)
   165  	for i := range connID {
   166  		connID[i] = byte(i)
   167  	}
   168  	for _, test := range []struct {
   169  		desc    string
   170  		num     packetNumber
   171  		payload []byte
   172  		k       updatingKeyPair
   173  	}{{
   174  		desc:    "1-byte number, AES128",
   175  		num:     0, // 1-byte encoding,
   176  		payload: []byte("payload"),
   177  		k:       aes128Keys,
   178  	}, {
   179  		desc:    "2-byte number, AES256",
   180  		num:     0x100, // 2-byte encoding
   181  		payload: []byte("payload"),
   182  		k:       aes256Keys,
   183  	}, {
   184  		desc:    "3-byte number, ChaCha20Poly1305",
   185  		num:     0x10000, // 3-byte encoding
   186  		payload: []byte("payload"),
   187  		k:       chachaKeys,
   188  	}, {
   189  		desc:    "4-byte number, ChaCha20Poly1305",
   190  		num:     0x1000000, // 4-byte encoding
   191  		payload: []byte{0},
   192  		k:       chachaKeys,
   193  	}} {
   194  		t.Run(test.desc, func(t *testing.T) {
   195  			var w packetWriter
   196  			w.reset(1200)
   197  			w.start1RTTPacket(test.num, 0, connID)
   198  			w.b = append(w.b, test.payload...)
   199  			w.finish1RTTPacket(test.num, 0, connID, &test.k)
   200  			pkt := w.datagram()
   201  			p, err := parse1RTTPacket(pkt, &test.k, connIDLen, 0)
   202  			if err != nil {
   203  				t.Errorf("parse1RTTPacket: err=%v, want nil", err)
   204  			}
   205  			if p.num != test.num || !bytes.Equal(p.payload, test.payload) {
   206  				t.Errorf("Round-trip encode/decode did not preserve packet.\nsent: num=%v, payload={%x}\ngot: num=%v, payload={%x}", test.num, test.payload, p.num, p.payload)
   207  			}
   208  		})
   209  	}
   210  }
   211  
   212  func TestFrameEncodeDecode(t *testing.T) {
   213  	for _, test := range []struct {
   214  		s         string
   215  		j         string
   216  		f         debugFrame
   217  		b         []byte
   218  		truncated []byte
   219  	}{{
   220  		s: "PADDING*1",
   221  		j: `{"frame_type":"padding","length":1}`,
   222  		f: debugFramePadding{
   223  			size: 1,
   224  		},
   225  		b: []byte{
   226  			0x00, // Type (i) = 0x00,
   227  
   228  		},
   229  	}, {
   230  		s: "PING",
   231  		j: `{"frame_type":"ping"}`,
   232  		f: debugFramePing{},
   233  		b: []byte{
   234  			0x01, // TYPE(i) = 0x01
   235  		},
   236  	}, {
   237  		s: "ACK Delay=10 [0,16) [17,32) [48,64)",
   238  		j: `"error: debugFrameAck should not appear as a slog Value"`,
   239  		f: debugFrameAck{
   240  			ackDelay: 10,
   241  			ranges: []i64range[packetNumber]{
   242  				{0x00, 0x10},
   243  				{0x11, 0x20},
   244  				{0x30, 0x40},
   245  			},
   246  		},
   247  		b: []byte{
   248  			0x02, // TYPE (i) = 0x02
   249  			0x3f, // Largest Acknowledged (i)
   250  			10,   // ACK Delay (i)
   251  			0x02, // ACK Range Count (i)
   252  			0x0f, // First ACK Range (i)
   253  			0x0f, // Gap (i)
   254  			0x0e, // ACK Range Length (i)
   255  			0x00, // Gap (i)
   256  			0x0f, // ACK Range Length (i)
   257  		},
   258  		truncated: []byte{
   259  			0x02, // TYPE (i) = 0x02
   260  			0x3f, // Largest Acknowledged (i)
   261  			10,   // ACK Delay (i)
   262  			0x01, // ACK Range Count (i)
   263  			0x0f, // First ACK Range (i)
   264  			0x0f, // Gap (i)
   265  			0x0e, // ACK Range Length (i)
   266  		},
   267  	}, {
   268  		s: "RESET_STREAM ID=1 Code=2 FinalSize=3",
   269  		j: `{"frame_type":"reset_stream","stream_id":1,"final_size":3}`,
   270  		f: debugFrameResetStream{
   271  			id:        1,
   272  			code:      2,
   273  			finalSize: 3,
   274  		},
   275  		b: []byte{
   276  			0x04, // TYPE(i) = 0x04
   277  			0x01, // Stream ID (i),
   278  			0x02, // Application Protocol Error Code (i),
   279  			0x03, // Final Size (i),
   280  		},
   281  	}, {
   282  		s: "STOP_SENDING ID=1 Code=2",
   283  		j: `{"frame_type":"stop_sending","stream_id":1,"error_code":2}`,
   284  		f: debugFrameStopSending{
   285  			id:   1,
   286  			code: 2,
   287  		},
   288  		b: []byte{
   289  			0x05, // TYPE(i) = 0x05
   290  			0x01, // Stream ID (i),
   291  			0x02, // Application Protocol Error Code (i),
   292  		},
   293  	}, {
   294  		s: "CRYPTO Offset=1 Length=2",
   295  		j: `{"frame_type":"crypto","offset":1,"length":2}`,
   296  		f: debugFrameCrypto{
   297  			off:  1,
   298  			data: []byte{3, 4},
   299  		},
   300  		b: []byte{
   301  			0x06,       // Type (i) = 0x06,
   302  			0x01,       // Offset (i),
   303  			0x02,       // Length (i),
   304  			0x03, 0x04, // Crypto Data (..),
   305  		},
   306  		truncated: []byte{
   307  			0x06, // Type (i) = 0x06,
   308  			0x01, // Offset (i),
   309  			0x01, // Length (i),
   310  			0x03,
   311  		},
   312  	}, {
   313  		s: "NEW_TOKEN Token=0304",
   314  		j: `{"frame_type":"new_token","token":"0304"}`,
   315  		f: debugFrameNewToken{
   316  			token: []byte{3, 4},
   317  		},
   318  		b: []byte{
   319  			0x07,       // Type (i) = 0x07,
   320  			0x02,       // Token Length (i),
   321  			0x03, 0x04, // Token (..),
   322  		},
   323  	}, {
   324  		s: "STREAM ID=1 Offset=0 Length=0",
   325  		j: `{"frame_type":"stream","stream_id":1,"offset":0,"length":0}`,
   326  		f: debugFrameStream{
   327  			id:   1,
   328  			fin:  false,
   329  			off:  0,
   330  			data: []byte{},
   331  		},
   332  		b: []byte{
   333  			0x0a, // Type (i) = 0x08..0x0f,
   334  			0x01, // Stream ID (i),
   335  			// [Offset (i)],
   336  			0x00, // [Length (i)],
   337  			// Stream Data (..),
   338  		},
   339  	}, {
   340  		s: "STREAM ID=100 Offset=4 Length=3",
   341  		j: `{"frame_type":"stream","stream_id":100,"offset":4,"length":3}`,
   342  		f: debugFrameStream{
   343  			id:   100,
   344  			fin:  false,
   345  			off:  4,
   346  			data: []byte{0xa0, 0xa1, 0xa2},
   347  		},
   348  		b: []byte{
   349  			0x0e,       // Type (i) = 0x08..0x0f,
   350  			0x40, 0x64, // Stream ID (i),
   351  			0x04,             // [Offset (i)],
   352  			0x03,             // [Length (i)],
   353  			0xa0, 0xa1, 0xa2, // Stream Data (..),
   354  		},
   355  		truncated: []byte{
   356  			0x0e,       // Type (i) = 0x08..0x0f,
   357  			0x40, 0x64, // Stream ID (i),
   358  			0x04,       // [Offset (i)],
   359  			0x02,       // [Length (i)],
   360  			0xa0, 0xa1, // Stream Data (..),
   361  		},
   362  	}, {
   363  		s: "STREAM ID=100 FIN Offset=4 Length=3",
   364  		j: `{"frame_type":"stream","stream_id":100,"offset":4,"length":3,"fin":true}`,
   365  		f: debugFrameStream{
   366  			id:   100,
   367  			fin:  true,
   368  			off:  4,
   369  			data: []byte{0xa0, 0xa1, 0xa2},
   370  		},
   371  		b: []byte{
   372  			0x0f,       // Type (i) = 0x08..0x0f,
   373  			0x40, 0x64, // Stream ID (i),
   374  			0x04,             // [Offset (i)],
   375  			0x03,             // [Length (i)],
   376  			0xa0, 0xa1, 0xa2, // Stream Data (..),
   377  		},
   378  		truncated: []byte{
   379  			0x0e,       // Type (i) = 0x08..0x0f,
   380  			0x40, 0x64, // Stream ID (i),
   381  			0x04,       // [Offset (i)],
   382  			0x02,       // [Length (i)],
   383  			0xa0, 0xa1, // Stream Data (..),
   384  		},
   385  	}, {
   386  		s: "STREAM ID=1 FIN Offset=100 Length=0",
   387  		j: `{"frame_type":"stream","stream_id":1,"offset":100,"length":0,"fin":true}`,
   388  		f: debugFrameStream{
   389  			id:   1,
   390  			fin:  true,
   391  			off:  100,
   392  			data: []byte{},
   393  		},
   394  		b: []byte{
   395  			0x0f,       // Type (i) = 0x08..0x0f,
   396  			0x01,       // Stream ID (i),
   397  			0x40, 0x64, // [Offset (i)],
   398  			0x00, // [Length (i)],
   399  			// Stream Data (..),
   400  		},
   401  	}, {
   402  		s: "MAX_DATA Max=10",
   403  		j: `{"frame_type":"max_data","maximum":10}`,
   404  		f: debugFrameMaxData{
   405  			max: 10,
   406  		},
   407  		b: []byte{
   408  			0x10, // Type (i) = 0x10,
   409  			0x0a, // Maximum Data (i),
   410  		},
   411  	}, {
   412  		s: "MAX_STREAM_DATA ID=1 Max=10",
   413  		j: `{"frame_type":"max_stream_data","stream_id":1,"maximum":10}`,
   414  		f: debugFrameMaxStreamData{
   415  			id:  1,
   416  			max: 10,
   417  		},
   418  		b: []byte{
   419  			0x11, // Type (i) = 0x11,
   420  			0x01, // Stream ID (i),
   421  			0x0a, // Maximum Stream Data (i),
   422  		},
   423  	}, {
   424  		s: "MAX_STREAMS Type=bidi Max=1",
   425  		j: `{"frame_type":"max_streams","stream_type":"bidirectional","maximum":1}`,
   426  		f: debugFrameMaxStreams{
   427  			streamType: bidiStream,
   428  			max:        1,
   429  		},
   430  		b: []byte{
   431  			0x12, //   Type (i) = 0x12..0x13,
   432  			0x01, // Maximum Streams (i),
   433  		},
   434  	}, {
   435  		s: "MAX_STREAMS Type=uni Max=1",
   436  		j: `{"frame_type":"max_streams","stream_type":"unidirectional","maximum":1}`,
   437  		f: debugFrameMaxStreams{
   438  			streamType: uniStream,
   439  			max:        1,
   440  		},
   441  		b: []byte{
   442  			0x13, //   Type (i) = 0x12..0x13,
   443  			0x01, // Maximum Streams (i),
   444  		},
   445  	}, {
   446  		s: "DATA_BLOCKED Max=1",
   447  		j: `{"frame_type":"data_blocked","limit":1}`,
   448  		f: debugFrameDataBlocked{
   449  			max: 1,
   450  		},
   451  		b: []byte{
   452  			0x14, // Type (i) = 0x14,
   453  			0x01, // Maximum Data (i),
   454  		},
   455  	}, {
   456  		s: "STREAM_DATA_BLOCKED ID=1 Max=2",
   457  		j: `{"frame_type":"stream_data_blocked","stream_id":1,"limit":2}`,
   458  		f: debugFrameStreamDataBlocked{
   459  			id:  1,
   460  			max: 2,
   461  		},
   462  		b: []byte{
   463  			0x15, // Type (i) = 0x15,
   464  			0x01, // Stream ID (i),
   465  			0x02, // Maximum Stream Data (i),
   466  		},
   467  	}, {
   468  		s: "STREAMS_BLOCKED Type=bidi Max=1",
   469  		j: `{"frame_type":"streams_blocked","stream_type":"bidirectional","limit":1}`,
   470  		f: debugFrameStreamsBlocked{
   471  			streamType: bidiStream,
   472  			max:        1,
   473  		},
   474  		b: []byte{
   475  			0x16, // Type (i) = 0x16..0x17,
   476  			0x01, // Maximum Streams (i),
   477  		},
   478  	}, {
   479  		s: "STREAMS_BLOCKED Type=uni Max=1",
   480  		j: `{"frame_type":"streams_blocked","stream_type":"unidirectional","limit":1}`,
   481  		f: debugFrameStreamsBlocked{
   482  			streamType: uniStream,
   483  			max:        1,
   484  		},
   485  		b: []byte{
   486  			0x17, // Type (i) = 0x16..0x17,
   487  			0x01, // Maximum Streams (i),
   488  		},
   489  	}, {
   490  		s: "NEW_CONNECTION_ID Seq=3 Retire=2 ID=a0a1a2a3 Token=0102030405060708090a0b0c0d0e0f10",
   491  		j: `{"frame_type":"new_connection_id","sequence_number":3,"retire_prior_to":2,"connection_id":"a0a1a2a3","stateless_reset_token":"0102030405060708090a0b0c0d0e0f10"}`,
   492  		f: debugFrameNewConnectionID{
   493  			seq:           3,
   494  			retirePriorTo: 2,
   495  			connID:        []byte{0xa0, 0xa1, 0xa2, 0xa3},
   496  			token:         [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
   497  		},
   498  		b: []byte{
   499  			0x18,                   // Type (i) = 0x18,
   500  			0x03,                   // Sequence Number (i),
   501  			0x02,                   // Retire Prior To (i),
   502  			0x04,                   // Length (8),
   503  			0xa0, 0xa1, 0xa2, 0xa3, // Connection ID (8..160),
   504  			1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // Stateless Reset Token (128),
   505  		},
   506  	}, {
   507  		s: "RETIRE_CONNECTION_ID Seq=1",
   508  		j: `{"frame_type":"retire_connection_id","sequence_number":1}`,
   509  		f: debugFrameRetireConnectionID{
   510  			seq: 1,
   511  		},
   512  		b: []byte{
   513  			0x19, // Type (i) = 0x19,
   514  			0x01, // Sequence Number (i),
   515  		},
   516  	}, {
   517  		s: "PATH_CHALLENGE Data=0123456789abcdef",
   518  		j: `{"frame_type":"path_challenge","data":"0123456789abcdef"}`,
   519  		f: debugFramePathChallenge{
   520  			data: pathChallengeData{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
   521  		},
   522  		b: []byte{
   523  			0x1a,                                           // Type (i) = 0x1a,
   524  			0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // Data (64),
   525  		},
   526  	}, {
   527  		s: "PATH_RESPONSE Data=0123456789abcdef",
   528  		j: `{"frame_type":"path_response","data":"0123456789abcdef"}`,
   529  		f: debugFramePathResponse{
   530  			data: pathChallengeData{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
   531  		},
   532  		b: []byte{
   533  			0x1b,                                           // Type (i) = 0x1b,
   534  			0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // Data (64),
   535  		},
   536  	}, {
   537  		s: `CONNECTION_CLOSE Code=INTERNAL_ERROR FrameType=2 Reason="oops"`,
   538  		j: `{"frame_type":"connection_close","error_space":"transport","error_code_value":1,"reason":"oops"}`,
   539  		f: debugFrameConnectionCloseTransport{
   540  			code:      1,
   541  			frameType: 2,
   542  			reason:    "oops",
   543  		},
   544  		b: []byte{
   545  			0x1c,               // Type (i) = 0x1c..0x1d,
   546  			0x01,               // Error Code (i),
   547  			0x02,               // [Frame Type (i)],
   548  			0x04,               // Reason Phrase Length (i),
   549  			'o', 'o', 'p', 's', // Reason Phrase (..),
   550  		},
   551  	}, {
   552  		s: `CONNECTION_CLOSE AppCode=1 Reason="oops"`,
   553  		j: `{"frame_type":"connection_close","error_space":"application","error_code_value":1,"reason":"oops"}`,
   554  		f: debugFrameConnectionCloseApplication{
   555  			code:   1,
   556  			reason: "oops",
   557  		},
   558  		b: []byte{
   559  			0x1d,               // Type (i) = 0x1c..0x1d,
   560  			0x01,               // Error Code (i),
   561  			0x04,               // Reason Phrase Length (i),
   562  			'o', 'o', 'p', 's', // Reason Phrase (..),
   563  		},
   564  	}, {
   565  		s: "HANDSHAKE_DONE",
   566  		j: `{"frame_type":"handshake_done"}`,
   567  		f: debugFrameHandshakeDone{},
   568  		b: []byte{
   569  			0x1e, // Type (i) = 0x1e,
   570  		},
   571  	}} {
   572  		var w packetWriter
   573  		w.reset(1200)
   574  		w.start1RTTPacket(0, 0, nil)
   575  		w.pktLim = w.payOff + len(test.b)
   576  		if added := test.f.write(&w); !added {
   577  			t.Errorf("encoding %v with %v bytes available: write unexpectedly failed", test.f, len(test.b))
   578  		}
   579  		if got, want := w.payload(), test.b; !bytes.Equal(got, want) {
   580  			t.Errorf("encoding %v:\ngot  {%x}\nwant {%x}", test.f, got, want)
   581  		}
   582  		gotf, n := parseDebugFrame(test.b)
   583  		if n != len(test.b) || !reflect.DeepEqual(gotf, test.f) {
   584  			t.Errorf("decoding {%x}:\ndecoded %v bytes, want %v\ngot:  %v\nwant: %v", test.b, n, len(test.b), gotf, test.f)
   585  		}
   586  		if got, want := test.f.String(), test.s; got != want {
   587  			t.Errorf("frame.String():\ngot  %q\nwant %q", got, want)
   588  		}
   589  		if got, want := frameJSON(test.f), test.j; got != want {
   590  			t.Errorf("frame.LogValue():\ngot  %q\nwant %q", got, want)
   591  		}
   592  
   593  		// Try encoding the frame into too little space.
   594  		// Most frames will result in an error; some (like STREAM frames) will truncate
   595  		// the data written.
   596  		w.reset(1200)
   597  		w.start1RTTPacket(0, 0, nil)
   598  		w.pktLim = w.payOff + len(test.b) - 1
   599  		if added := test.f.write(&w); added {
   600  			if test.truncated == nil {
   601  				t.Errorf("encoding %v with %v-1 bytes available: write unexpectedly succeeded", test.f, len(test.b))
   602  			} else if got, want := w.payload(), test.truncated; !bytes.Equal(got, want) {
   603  				t.Errorf("encoding %v with %v-1 bytes available:\ngot  {%x}\nwant {%x}", test.f, len(test.b), got, want)
   604  			}
   605  		}
   606  
   607  		// Try parsing truncated data.
   608  		for i := 0; i < len(test.b); i++ {
   609  			f, n := parseDebugFrame(test.b[:i])
   610  			if n >= 0 {
   611  				t.Errorf("decoding truncated frame {%x}:\ngot: %v\nwant error", test.b[:i], f)
   612  			}
   613  		}
   614  	}
   615  }
   616  
   617  func TestFrameScaledAck(t *testing.T) {
   618  	for _, test := range []struct {
   619  		j string
   620  		f debugFrameScaledAck
   621  	}{{
   622  		j: `{"frame_type":"ack","acked_ranges":[[0,15],[17],[48,63]],"ack_delay":10.000000}`,
   623  		f: debugFrameScaledAck{
   624  			ackDelay: 10 * time.Millisecond,
   625  			ranges: []i64range[packetNumber]{
   626  				{0x00, 0x10},
   627  				{0x11, 0x12},
   628  				{0x30, 0x40},
   629  			},
   630  		},
   631  	}} {
   632  		if got, want := frameJSON(test.f), test.j; got != want {
   633  			t.Errorf("frame.LogValue():\ngot  %q\nwant %q", got, want)
   634  		}
   635  	}
   636  }
   637  
   638  func frameJSON(f slog.LogValuer) string {
   639  	var buf bytes.Buffer
   640  	h := qlog.NewJSONHandler(qlog.HandlerOptions{
   641  		Level: QLogLevelFrame,
   642  		NewTrace: func(info qlog.TraceInfo) (io.WriteCloser, error) {
   643  			return nopCloseWriter{&buf}, nil
   644  		},
   645  	})
   646  	// Log the frame, and then trim out everything but the frame from the log.
   647  	slog.New(h).Info("message", slog.Any("frame", f))
   648  	_, b, _ := bytes.Cut(buf.Bytes(), []byte(`"frame":`))
   649  	b = bytes.TrimSuffix(b, []byte("}}\n"))
   650  	return string(b)
   651  }
   652  
   653  func TestFrameDecode(t *testing.T) {
   654  	for _, test := range []struct {
   655  		desc string
   656  		want debugFrame
   657  		b    []byte
   658  	}{{
   659  		desc: "STREAM frame with LEN bit unset",
   660  		want: debugFrameStream{
   661  			id:   1,
   662  			fin:  false,
   663  			data: []byte{0x01, 0x02, 0x03},
   664  		},
   665  		b: []byte{
   666  			0x08, // Type (i) = 0x08..0x0f,
   667  			0x01, // Stream ID (i),
   668  			// [Offset (i)],
   669  			// [Length (i)],
   670  			0x01, 0x02, 0x03, // Stream Data (..),
   671  		},
   672  	}, {
   673  		desc: "ACK frame with ECN counts",
   674  		want: debugFrameAck{
   675  			ackDelay: 10,
   676  			ranges: []i64range[packetNumber]{
   677  				{0, 1},
   678  			},
   679  		},
   680  		b: []byte{
   681  			0x03,             // TYPE (i) = 0x02..0x03
   682  			0x00,             // Largest Acknowledged (i)
   683  			10,               // ACK Delay (i)
   684  			0x00,             // ACK Range Count (i)
   685  			0x00,             // First ACK Range (i)
   686  			0x01, 0x02, 0x03, // [ECN Counts (..)],
   687  		},
   688  	}} {
   689  		got, n := parseDebugFrame(test.b)
   690  		if n != len(test.b) || !reflect.DeepEqual(got, test.want) {
   691  			t.Errorf("decoding {%x}:\ndecoded %v bytes, want %v\ngot:  %v\nwant: %v", test.b, n, len(test.b), got, test.want)
   692  		}
   693  	}
   694  }
   695  
   696  func TestFrameDecodeErrors(t *testing.T) {
   697  	for _, test := range []struct {
   698  		name string
   699  		b    []byte
   700  	}{{
   701  		name: "ACK [-1,0]",
   702  		b: []byte{
   703  			0x02, // TYPE (i) = 0x02
   704  			0x00, // Largest Acknowledged (i)
   705  			0x00, // ACK Delay (i)
   706  			0x00, // ACK Range Count (i)
   707  			0x01, // First ACK Range (i)
   708  		},
   709  	}, {
   710  		name: "ACK [-1,16]",
   711  		b: []byte{
   712  			0x02, // TYPE (i) = 0x02
   713  			0x10, // Largest Acknowledged (i)
   714  			0x00, // ACK Delay (i)
   715  			0x00, // ACK Range Count (i)
   716  			0x11, // First ACK Range (i)
   717  		},
   718  	}, {
   719  		name: "ACK [-1,0],[1,2]",
   720  		b: []byte{
   721  			0x02, // TYPE (i) = 0x02
   722  			0x02, // Largest Acknowledged (i)
   723  			0x00, // ACK Delay (i)
   724  			0x01, // ACK Range Count (i)
   725  			0x00, // First ACK Range (i)
   726  			0x01, // Gap (i)
   727  			0x01, // ACK Range Length (i)
   728  		},
   729  	}, {
   730  		name: "NEW_TOKEN with zero-length token",
   731  		b: []byte{
   732  			0x07, // Type (i) = 0x07,
   733  			0x00, // Token Length (i),
   734  		},
   735  	}, {
   736  		name: "MAX_STREAMS with too many streams",
   737  		b: func() []byte {
   738  			// https://www.rfc-editor.org/rfc/rfc9000.html#section-19.11-5.2.1
   739  			return appendVarint([]byte{frameTypeMaxStreamsBidi}, (1<<60)+1)
   740  		}(),
   741  	}, {
   742  		name: "NEW_CONNECTION_ID too small",
   743  		b: []byte{
   744  			0x18, // Type (i) = 0x18,
   745  			0x03, // Sequence Number (i),
   746  			0x02, // Retire Prior To (i),
   747  			0x00, // Length (8),
   748  			// Connection ID (8..160),
   749  			1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // Stateless Reset Token (128),
   750  		},
   751  	}, {
   752  		name: "NEW_CONNECTION_ID too large",
   753  		b: []byte{
   754  			0x18, // Type (i) = 0x18,
   755  			0x03, // Sequence Number (i),
   756  			0x02, // Retire Prior To (i),
   757  			21,   // Length (8),
   758  			1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
   759  			11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, // Connection ID (8..160),
   760  			1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // Stateless Reset Token (128),
   761  		},
   762  	}, {
   763  		name: "NEW_CONNECTION_ID sequence smaller than retire",
   764  		b: []byte{
   765  			0x18,       // Type (i) = 0x18,
   766  			0x02,       // Sequence Number (i),
   767  			0x03,       // Retire Prior To (i),
   768  			0x02,       // Length (8),
   769  			0xff, 0xff, // Connection ID (8..160),
   770  			1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // Stateless Reset Token (128),
   771  		},
   772  	}} {
   773  		f, n := parseDebugFrame(test.b)
   774  		if n >= 0 {
   775  			t.Errorf("%v: no error when parsing invalid frame {%x}\ngot: %v", test.name, test.b, f)
   776  		}
   777  	}
   778  }
   779  
   780  func FuzzParseLongHeaderPacket(f *testing.F) {
   781  	cid := unhex(`0000000000000000`)
   782  	initialServerKeys := initialKeys(cid, clientSide).r
   783  	f.Fuzz(func(t *testing.T, in []byte) {
   784  		parseLongHeaderPacket(in, initialServerKeys, 0)
   785  	})
   786  }
   787  
   788  func FuzzFrameDecode(f *testing.F) {
   789  	f.Fuzz(func(t *testing.T, in []byte) {
   790  		parseDebugFrame(in)
   791  	})
   792  }