golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/quic/conn_loss_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  	"context"
    11  	"crypto/tls"
    12  	"fmt"
    13  	"testing"
    14  )
    15  
    16  // Frames may be retransmitted either when the packet containing the frame is lost, or on PTO.
    17  // lostFrameTest runs a test in both configurations.
    18  func lostFrameTest(t *testing.T, f func(t *testing.T, pto bool)) {
    19  	t.Run("lost", func(t *testing.T) {
    20  		f(t, false)
    21  	})
    22  	t.Run("pto", func(t *testing.T) {
    23  		f(t, true)
    24  	})
    25  }
    26  
    27  // triggerLossOrPTO causes the conn to declare the last sent packet lost,
    28  // or advances to the PTO timer.
    29  func (tc *testConn) triggerLossOrPTO(ptype packetType, pto bool) {
    30  	tc.t.Helper()
    31  	if pto {
    32  		if !tc.conn.loss.ptoTimerArmed {
    33  			tc.t.Fatalf("PTO timer not armed, expected it to be")
    34  		}
    35  		if *testVV {
    36  			tc.t.Logf("advancing to PTO timer")
    37  		}
    38  		tc.advanceTo(tc.conn.loss.timer)
    39  		return
    40  	}
    41  	if *testVV {
    42  		*testVV = false
    43  		defer func() {
    44  			tc.t.Logf("cause conn to declare last packet lost")
    45  			*testVV = true
    46  		}()
    47  	}
    48  	defer func(ignoreFrames map[byte]bool) {
    49  		tc.ignoreFrames = ignoreFrames
    50  	}(tc.ignoreFrames)
    51  	tc.ignoreFrames = map[byte]bool{
    52  		frameTypeAck:     true,
    53  		frameTypePadding: true,
    54  	}
    55  	// Send three packets containing PINGs, and then respond with an ACK for the
    56  	// last one. This puts the last packet before the PINGs outside the packet
    57  	// reordering threshold, and it will be declared lost.
    58  	const lossThreshold = 3
    59  	var num packetNumber
    60  	for i := 0; i < lossThreshold; i++ {
    61  		tc.conn.ping(spaceForPacketType(ptype))
    62  		d := tc.readDatagram()
    63  		if d == nil {
    64  			tc.t.Fatalf("conn is idle; want PING frame")
    65  		}
    66  		if d.packets[0].ptype != ptype {
    67  			tc.t.Fatalf("conn sent %v packet; want %v", d.packets[0].ptype, ptype)
    68  		}
    69  		num = d.packets[0].num
    70  	}
    71  	tc.writeFrames(ptype, debugFrameAck{
    72  		ranges: []i64range[packetNumber]{
    73  			{num, num + 1},
    74  		},
    75  	})
    76  }
    77  
    78  func TestLostResetStreamFrame(t *testing.T) {
    79  	// "Cancellation of stream transmission, as carried in a RESET_STREAM frame,
    80  	// is sent until acknowledged or until all stream data is acknowledged by the peer [...]"
    81  	// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.4
    82  	lostFrameTest(t, func(t *testing.T, pto bool) {
    83  		tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters)
    84  		tc.ignoreFrame(frameTypeAck)
    85  
    86  		s.Reset(1)
    87  		tc.wantFrame("reset stream",
    88  			packetType1RTT, debugFrameResetStream{
    89  				id:   s.id,
    90  				code: 1,
    91  			})
    92  
    93  		tc.triggerLossOrPTO(packetType1RTT, pto)
    94  		tc.wantFrame("resent RESET_STREAM frame",
    95  			packetType1RTT, debugFrameResetStream{
    96  				id:   s.id,
    97  				code: 1,
    98  			})
    99  	})
   100  }
   101  
   102  func TestLostStopSendingFrame(t *testing.T) {
   103  	// "[...] a request to cancel stream transmission, as encoded in a STOP_SENDING frame,
   104  	// is sent until the receiving part of the stream enters either a "Data Recvd" or
   105  	// "Reset Recvd" state [...]"
   106  	// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.5
   107  	//
   108  	// Technically, we can stop sending a STOP_SENDING frame if the peer sends
   109  	// us all the data for the stream or resets it. We don't bother tracking this,
   110  	// however, so we'll keep sending the frame until it is acked. This is harmless.
   111  	lostFrameTest(t, func(t *testing.T, pto bool) {
   112  		tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, permissiveTransportParameters)
   113  		tc.ignoreFrame(frameTypeAck)
   114  
   115  		s.CloseRead()
   116  		tc.wantFrame("stream is read-closed",
   117  			packetType1RTT, debugFrameStopSending{
   118  				id: s.id,
   119  			})
   120  
   121  		tc.triggerLossOrPTO(packetType1RTT, pto)
   122  		tc.wantFrame("resent STOP_SENDING frame",
   123  			packetType1RTT, debugFrameStopSending{
   124  				id: s.id,
   125  			})
   126  	})
   127  }
   128  
   129  func TestLostCryptoFrame(t *testing.T) {
   130  	// "Data sent in CRYPTO frames is retransmitted [...] until all data has been acknowledged."
   131  	// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.1
   132  	lostFrameTest(t, func(t *testing.T, pto bool) {
   133  		tc := newTestConn(t, clientSide)
   134  		tc.ignoreFrame(frameTypeAck)
   135  
   136  		tc.wantFrame("client sends Initial CRYPTO frame",
   137  			packetTypeInitial, debugFrameCrypto{
   138  				data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial],
   139  			})
   140  		tc.triggerLossOrPTO(packetTypeInitial, pto)
   141  		tc.wantFrame("client resends Initial CRYPTO frame",
   142  			packetTypeInitial, debugFrameCrypto{
   143  				data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial],
   144  			})
   145  
   146  		tc.writeFrames(packetTypeInitial,
   147  			debugFrameCrypto{
   148  				data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial],
   149  			})
   150  		tc.writeFrames(packetTypeHandshake,
   151  			debugFrameCrypto{
   152  				data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake],
   153  			})
   154  
   155  		tc.wantFrame("client sends Handshake CRYPTO frame",
   156  			packetTypeHandshake, debugFrameCrypto{
   157  				data: tc.cryptoDataOut[tls.QUICEncryptionLevelHandshake],
   158  			})
   159  		tc.wantFrame("client provides server with an additional connection ID",
   160  			packetType1RTT, debugFrameNewConnectionID{
   161  				seq:    1,
   162  				connID: testLocalConnID(1),
   163  				token:  testLocalStatelessResetToken(1),
   164  			})
   165  		tc.triggerLossOrPTO(packetTypeHandshake, pto)
   166  		tc.wantFrame("client resends Handshake CRYPTO frame",
   167  			packetTypeHandshake, debugFrameCrypto{
   168  				data: tc.cryptoDataOut[tls.QUICEncryptionLevelHandshake],
   169  			})
   170  	})
   171  }
   172  
   173  func TestLostStreamFrameEmpty(t *testing.T) {
   174  	// A STREAM frame opening a stream, but containing no stream data, should
   175  	// be retransmitted if lost.
   176  	lostFrameTest(t, func(t *testing.T, pto bool) {
   177  		ctx := canceledContext()
   178  		tc := newTestConn(t, clientSide, permissiveTransportParameters)
   179  		tc.handshake()
   180  		tc.ignoreFrame(frameTypeAck)
   181  
   182  		c, err := tc.conn.NewStream(ctx)
   183  		if err != nil {
   184  			t.Fatalf("NewStream: %v", err)
   185  		}
   186  		c.Flush() // open the stream
   187  		tc.wantFrame("created bidirectional stream 0",
   188  			packetType1RTT, debugFrameStream{
   189  				id:   newStreamID(clientSide, bidiStream, 0),
   190  				data: []byte{},
   191  			})
   192  
   193  		tc.triggerLossOrPTO(packetType1RTT, pto)
   194  		tc.wantFrame("resent stream frame",
   195  			packetType1RTT, debugFrameStream{
   196  				id:   newStreamID(clientSide, bidiStream, 0),
   197  				data: []byte{},
   198  			})
   199  	})
   200  }
   201  
   202  func TestLostStreamWithData(t *testing.T) {
   203  	// "Application data sent in STREAM frames is retransmitted in new STREAM
   204  	// frames unless the endpoint has sent a RESET_STREAM for that stream."
   205  	// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.2
   206  	//
   207  	// TODO: Lost stream frame after RESET_STREAM
   208  	lostFrameTest(t, func(t *testing.T, pto bool) {
   209  		data := []byte{0, 1, 2, 3, 4, 5, 6, 7}
   210  		tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) {
   211  			p.initialMaxStreamsUni = 1
   212  			p.initialMaxData = 1 << 20
   213  			p.initialMaxStreamDataUni = 1 << 20
   214  		})
   215  		s.Write(data[:4])
   216  		s.Flush()
   217  		tc.wantFrame("send [0,4)",
   218  			packetType1RTT, debugFrameStream{
   219  				id:   s.id,
   220  				off:  0,
   221  				data: data[:4],
   222  			})
   223  		s.Write(data[4:8])
   224  		s.Flush()
   225  		tc.wantFrame("send [4,8)",
   226  			packetType1RTT, debugFrameStream{
   227  				id:   s.id,
   228  				off:  4,
   229  				data: data[4:8],
   230  			})
   231  		s.CloseWrite()
   232  		tc.wantFrame("send FIN",
   233  			packetType1RTT, debugFrameStream{
   234  				id:   s.id,
   235  				off:  8,
   236  				fin:  true,
   237  				data: []byte{},
   238  			})
   239  
   240  		tc.triggerLossOrPTO(packetType1RTT, pto)
   241  		tc.wantFrame("resend data",
   242  			packetType1RTT, debugFrameStream{
   243  				id:   s.id,
   244  				off:  0,
   245  				fin:  true,
   246  				data: data[:8],
   247  			})
   248  	})
   249  }
   250  
   251  func TestLostStreamPartialLoss(t *testing.T) {
   252  	// Conn sends four STREAM packets.
   253  	// ACKs are received for the packets containing bytes 0 and 2.
   254  	// The remaining packets are declared lost.
   255  	// The Conn resends only the lost data.
   256  	//
   257  	// This test doesn't have a PTO mode, because the ACK for the packet containing byte 2
   258  	// starts the loss timer for the packet containing byte 1, and the PTO timer is not
   259  	// armed when the loss timer is.
   260  	data := []byte{0, 1, 2, 3}
   261  	tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) {
   262  		p.initialMaxStreamsUni = 1
   263  		p.initialMaxData = 1 << 20
   264  		p.initialMaxStreamDataUni = 1 << 20
   265  	})
   266  	for i := range data {
   267  		s.Write(data[i : i+1])
   268  		s.Flush()
   269  		tc.wantFrame(fmt.Sprintf("send STREAM frame with byte %v", i),
   270  			packetType1RTT, debugFrameStream{
   271  				id:   s.id,
   272  				off:  int64(i),
   273  				data: data[i : i+1],
   274  			})
   275  		if i%2 == 0 {
   276  			tc.writeAckForLatest()
   277  		}
   278  	}
   279  	const pto = false
   280  	tc.triggerLossOrPTO(packetType1RTT, pto)
   281  	tc.wantFrame("resend byte 1",
   282  		packetType1RTT, debugFrameStream{
   283  			id:   s.id,
   284  			off:  1,
   285  			data: data[1:2],
   286  		})
   287  	tc.wantFrame("resend byte 3",
   288  		packetType1RTT, debugFrameStream{
   289  			id:   s.id,
   290  			off:  3,
   291  			data: data[3:4],
   292  		})
   293  	tc.wantIdle("no more frames sent after packet loss")
   294  }
   295  
   296  func TestLostMaxDataFrame(t *testing.T) {
   297  	// "An updated value is sent in a MAX_DATA frame if the packet
   298  	// containing the most recently sent MAX_DATA frame is declared lost [...]"
   299  	// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.7
   300  	lostFrameTest(t, func(t *testing.T, pto bool) {
   301  		const maxWindowSize = 32
   302  		buf := make([]byte, maxWindowSize)
   303  		tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) {
   304  			c.MaxConnReadBufferSize = 32
   305  		})
   306  
   307  		// We send MAX_DATA = 63.
   308  		tc.writeFrames(packetType1RTT, debugFrameStream{
   309  			id:   s.id,
   310  			off:  0,
   311  			data: make([]byte, maxWindowSize-1),
   312  		})
   313  		if n, err := s.Read(buf[:maxWindowSize]); err != nil || n != maxWindowSize-1 {
   314  			t.Fatalf("Read() = %v, %v; want %v, nil", n, err, maxWindowSize-1)
   315  		}
   316  		tc.wantFrame("conn window is extended after reading data",
   317  			packetType1RTT, debugFrameMaxData{
   318  				max: (maxWindowSize * 2) - 1,
   319  			})
   320  
   321  		// MAX_DATA = 64, which is only one more byte, so we don't send the frame.
   322  		tc.writeFrames(packetType1RTT, debugFrameStream{
   323  			id:   s.id,
   324  			off:  maxWindowSize - 1,
   325  			data: make([]byte, 1),
   326  		})
   327  		if n, err := s.Read(buf[:1]); err != nil || n != 1 {
   328  			t.Fatalf("Read() = %v, %v; want %v, nil", n, err, 1)
   329  		}
   330  		tc.wantIdle("read doesn't extend window enough to send another MAX_DATA")
   331  
   332  		// The MAX_DATA = 63 packet was lost, so we send 64.
   333  		tc.triggerLossOrPTO(packetType1RTT, pto)
   334  		tc.wantFrame("resent MAX_DATA includes most current value",
   335  			packetType1RTT, debugFrameMaxData{
   336  				max: maxWindowSize * 2,
   337  			})
   338  	})
   339  }
   340  
   341  func TestLostMaxStreamDataFrame(t *testing.T) {
   342  	// "[...] an updated value is sent when the packet containing
   343  	// the most recent MAX_STREAM_DATA frame for a stream is lost"
   344  	// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.8
   345  	lostFrameTest(t, func(t *testing.T, pto bool) {
   346  		const maxWindowSize = 32
   347  		buf := make([]byte, maxWindowSize)
   348  		tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) {
   349  			c.MaxStreamReadBufferSize = maxWindowSize
   350  		})
   351  
   352  		// We send MAX_STREAM_DATA = 63.
   353  		tc.writeFrames(packetType1RTT, debugFrameStream{
   354  			id:   s.id,
   355  			off:  0,
   356  			data: make([]byte, maxWindowSize-1),
   357  		})
   358  		if n, err := s.Read(buf[:maxWindowSize]); err != nil || n != maxWindowSize-1 {
   359  			t.Fatalf("Read() = %v, %v; want %v, nil", n, err, maxWindowSize-1)
   360  		}
   361  		tc.wantFrame("stream window is extended after reading data",
   362  			packetType1RTT, debugFrameMaxStreamData{
   363  				id:  s.id,
   364  				max: (maxWindowSize * 2) - 1,
   365  			})
   366  
   367  		// MAX_STREAM_DATA = 64, which is only one more byte, so we don't send the frame.
   368  		tc.writeFrames(packetType1RTT, debugFrameStream{
   369  			id:   s.id,
   370  			off:  maxWindowSize - 1,
   371  			data: make([]byte, 1),
   372  		})
   373  		if n, err := s.Read(buf); err != nil || n != 1 {
   374  			t.Fatalf("Read() = %v, %v; want %v, nil", n, err, 1)
   375  		}
   376  		tc.wantIdle("read doesn't extend window enough to send another MAX_STREAM_DATA")
   377  
   378  		// The MAX_STREAM_DATA = 63 packet was lost, so we send 64.
   379  		tc.triggerLossOrPTO(packetType1RTT, pto)
   380  		tc.wantFrame("resent MAX_STREAM_DATA includes most current value",
   381  			packetType1RTT, debugFrameMaxStreamData{
   382  				id:  s.id,
   383  				max: maxWindowSize * 2,
   384  			})
   385  	})
   386  }
   387  
   388  func TestLostMaxStreamDataFrameAfterStreamFinReceived(t *testing.T) {
   389  	// "An endpoint SHOULD stop sending MAX_STREAM_DATA frames when
   390  	// the receiving part of the stream enters a "Size Known" or "Reset Recvd" state."
   391  	// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.8
   392  	lostFrameTest(t, func(t *testing.T, pto bool) {
   393  		const maxWindowSize = 10
   394  		buf := make([]byte, maxWindowSize)
   395  		tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) {
   396  			c.MaxStreamReadBufferSize = maxWindowSize
   397  		})
   398  
   399  		tc.writeFrames(packetType1RTT, debugFrameStream{
   400  			id:   s.id,
   401  			off:  0,
   402  			data: make([]byte, maxWindowSize),
   403  		})
   404  		if n, err := s.Read(buf); err != nil || n != maxWindowSize {
   405  			t.Fatalf("Read() = %v, %v; want %v, nil", n, err, maxWindowSize)
   406  		}
   407  		tc.wantFrame("stream window is extended after reading data",
   408  			packetType1RTT, debugFrameMaxStreamData{
   409  				id:  s.id,
   410  				max: 2 * maxWindowSize,
   411  			})
   412  
   413  		tc.writeFrames(packetType1RTT, debugFrameStream{
   414  			id:  s.id,
   415  			off: maxWindowSize,
   416  			fin: true,
   417  		})
   418  
   419  		tc.ignoreFrame(frameTypePing)
   420  		tc.triggerLossOrPTO(packetType1RTT, pto)
   421  		tc.wantIdle("lost MAX_STREAM_DATA not resent for stream in 'size known'")
   422  	})
   423  }
   424  
   425  func TestLostMaxStreamsFrameMostRecent(t *testing.T) {
   426  	// "[...] an updated value is sent when a packet containing the
   427  	// most recent MAX_STREAMS for a stream type frame is declared lost [...]"
   428  	// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.9
   429  	testStreamTypes(t, "", func(t *testing.T, styp streamType) {
   430  		lostFrameTest(t, func(t *testing.T, pto bool) {
   431  			ctx := canceledContext()
   432  			tc := newTestConn(t, serverSide, func(c *Config) {
   433  				c.MaxUniRemoteStreams = 1
   434  				c.MaxBidiRemoteStreams = 1
   435  			})
   436  			tc.handshake()
   437  			tc.ignoreFrame(frameTypeAck)
   438  			tc.writeFrames(packetType1RTT, debugFrameStream{
   439  				id:  newStreamID(clientSide, styp, 0),
   440  				fin: true,
   441  			})
   442  			s, err := tc.conn.AcceptStream(ctx)
   443  			if err != nil {
   444  				t.Fatalf("AcceptStream() = %v", err)
   445  			}
   446  			s.SetWriteContext(ctx)
   447  			s.Close()
   448  			if styp == bidiStream {
   449  				tc.wantFrame("stream is closed",
   450  					packetType1RTT, debugFrameStream{
   451  						id:   s.id,
   452  						data: []byte{},
   453  						fin:  true,
   454  					})
   455  				tc.writeAckForAll()
   456  			}
   457  			tc.wantFrame("closing stream updates peer's MAX_STREAMS",
   458  				packetType1RTT, debugFrameMaxStreams{
   459  					streamType: styp,
   460  					max:        2,
   461  				})
   462  
   463  			tc.triggerLossOrPTO(packetType1RTT, pto)
   464  			tc.wantFrame("lost MAX_STREAMS is resent",
   465  				packetType1RTT, debugFrameMaxStreams{
   466  					streamType: styp,
   467  					max:        2,
   468  				})
   469  		})
   470  	})
   471  }
   472  
   473  func TestLostMaxStreamsFrameNotMostRecent(t *testing.T) {
   474  	// Send two MAX_STREAMS frames, lose the first one.
   475  	//
   476  	// No PTO mode for this test: The ack that causes the first frame
   477  	// to be lost arms the loss timer for the second, so the PTO timer is not armed.
   478  	const pto = false
   479  	ctx := canceledContext()
   480  	tc := newTestConn(t, serverSide, func(c *Config) {
   481  		c.MaxUniRemoteStreams = 2
   482  	})
   483  	tc.handshake()
   484  	tc.ignoreFrame(frameTypeAck)
   485  	for i := int64(0); i < 2; i++ {
   486  		tc.writeFrames(packetType1RTT, debugFrameStream{
   487  			id:  newStreamID(clientSide, uniStream, i),
   488  			fin: true,
   489  		})
   490  		s, err := tc.conn.AcceptStream(ctx)
   491  		if err != nil {
   492  			t.Fatalf("AcceptStream() = %v", err)
   493  		}
   494  		if err := s.Close(); err != nil {
   495  			t.Fatalf("stream.Close() = %v", err)
   496  		}
   497  		tc.wantFrame("closing stream updates peer's MAX_STREAMS",
   498  			packetType1RTT, debugFrameMaxStreams{
   499  				streamType: uniStream,
   500  				max:        3 + i,
   501  			})
   502  	}
   503  
   504  	// The second MAX_STREAMS frame is acked.
   505  	tc.writeAckForLatest()
   506  
   507  	// The first MAX_STREAMS frame is lost.
   508  	tc.conn.ping(appDataSpace)
   509  	tc.wantFrame("connection should send a PING frame",
   510  		packetType1RTT, debugFramePing{})
   511  	tc.triggerLossOrPTO(packetType1RTT, pto)
   512  	tc.wantIdle("superseded MAX_DATA is not resent on loss")
   513  }
   514  
   515  func TestLostStreamDataBlockedFrame(t *testing.T) {
   516  	// "A new [STREAM_DATA_BLOCKED] frame is sent if a packet containing
   517  	// the most recent frame for a scope is lost [...]"
   518  	// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.10
   519  	lostFrameTest(t, func(t *testing.T, pto bool) {
   520  		tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) {
   521  			p.initialMaxStreamsUni = 1
   522  			p.initialMaxData = 1 << 20
   523  		})
   524  
   525  		w := runAsync(tc, func(ctx context.Context) (int, error) {
   526  			return s.Write([]byte{0, 1, 2, 3})
   527  		})
   528  		defer w.cancel()
   529  		tc.wantFrame("write is blocked by flow control",
   530  			packetType1RTT, debugFrameStreamDataBlocked{
   531  				id:  s.id,
   532  				max: 0,
   533  			})
   534  
   535  		tc.writeFrames(packetType1RTT, debugFrameMaxStreamData{
   536  			id:  s.id,
   537  			max: 1,
   538  		})
   539  		tc.wantFrame("write makes some progress, but is still blocked by flow control",
   540  			packetType1RTT, debugFrameStreamDataBlocked{
   541  				id:  s.id,
   542  				max: 1,
   543  			})
   544  		tc.wantFrame("write consuming available window",
   545  			packetType1RTT, debugFrameStream{
   546  				id:   s.id,
   547  				off:  0,
   548  				data: []byte{0},
   549  			})
   550  
   551  		tc.triggerLossOrPTO(packetType1RTT, pto)
   552  		tc.wantFrame("STREAM_DATA_BLOCKED is resent",
   553  			packetType1RTT, debugFrameStreamDataBlocked{
   554  				id:  s.id,
   555  				max: 1,
   556  			})
   557  		tc.wantFrame("STREAM is resent as well",
   558  			packetType1RTT, debugFrameStream{
   559  				id:   s.id,
   560  				off:  0,
   561  				data: []byte{0},
   562  			})
   563  	})
   564  }
   565  
   566  func TestLostStreamDataBlockedFrameAfterStreamUnblocked(t *testing.T) {
   567  	// "A new [STREAM_DATA_BLOCKED] frame is sent [...] only while
   568  	// the endpoint is blocked on the corresponding limit."
   569  	// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.10
   570  	lostFrameTest(t, func(t *testing.T, pto bool) {
   571  		tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) {
   572  			p.initialMaxStreamsUni = 1
   573  			p.initialMaxData = 1 << 20
   574  		})
   575  
   576  		data := []byte{0, 1, 2, 3}
   577  		w := runAsync(tc, func(ctx context.Context) (int, error) {
   578  			return s.Write(data)
   579  		})
   580  		defer w.cancel()
   581  		tc.wantFrame("write is blocked by flow control",
   582  			packetType1RTT, debugFrameStreamDataBlocked{
   583  				id:  s.id,
   584  				max: 0,
   585  			})
   586  
   587  		tc.writeFrames(packetType1RTT, debugFrameMaxStreamData{
   588  			id:  s.id,
   589  			max: 10,
   590  		})
   591  		tc.wantFrame("write completes after flow control available",
   592  			packetType1RTT, debugFrameStream{
   593  				id:   s.id,
   594  				off:  0,
   595  				data: data,
   596  			})
   597  
   598  		tc.triggerLossOrPTO(packetType1RTT, pto)
   599  		tc.wantFrame("STREAM data is resent",
   600  			packetType1RTT, debugFrameStream{
   601  				id:   s.id,
   602  				off:  0,
   603  				data: data,
   604  			})
   605  		tc.wantIdle("STREAM_DATA_BLOCKED is not resent, since the stream is not blocked")
   606  	})
   607  }
   608  
   609  func TestLostNewConnectionIDFrame(t *testing.T) {
   610  	// "New connection IDs are [...] retransmitted if the packet containing them is lost."
   611  	// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.13
   612  	lostFrameTest(t, func(t *testing.T, pto bool) {
   613  		tc := newTestConn(t, serverSide)
   614  		tc.handshake()
   615  		tc.ignoreFrame(frameTypeAck)
   616  
   617  		tc.writeFrames(packetType1RTT,
   618  			debugFrameRetireConnectionID{
   619  				seq: 1,
   620  			})
   621  		tc.wantFrame("provide a new connection ID after peer retires old one",
   622  			packetType1RTT, debugFrameNewConnectionID{
   623  				seq:    2,
   624  				connID: testLocalConnID(2),
   625  				token:  testLocalStatelessResetToken(2),
   626  			})
   627  
   628  		tc.triggerLossOrPTO(packetType1RTT, pto)
   629  		tc.wantFrame("resend new connection ID",
   630  			packetType1RTT, debugFrameNewConnectionID{
   631  				seq:    2,
   632  				connID: testLocalConnID(2),
   633  				token:  testLocalStatelessResetToken(2),
   634  			})
   635  	})
   636  }
   637  
   638  func TestLostRetireConnectionIDFrame(t *testing.T) {
   639  	// "[...] retired connection IDs are [...] retransmitted
   640  	// if the packet containing them is lost."
   641  	// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.13
   642  	lostFrameTest(t, func(t *testing.T, pto bool) {
   643  		tc := newTestConn(t, clientSide)
   644  		tc.handshake()
   645  		tc.ignoreFrame(frameTypeAck)
   646  
   647  		tc.writeFrames(packetType1RTT,
   648  			debugFrameNewConnectionID{
   649  				seq:           2,
   650  				retirePriorTo: 1,
   651  				connID:        testPeerConnID(2),
   652  			})
   653  		tc.wantFrame("peer requested connection id be retired",
   654  			packetType1RTT, debugFrameRetireConnectionID{
   655  				seq: 0,
   656  			})
   657  
   658  		tc.triggerLossOrPTO(packetType1RTT, pto)
   659  		tc.wantFrame("resend RETIRE_CONNECTION_ID",
   660  			packetType1RTT, debugFrameRetireConnectionID{
   661  				seq: 0,
   662  			})
   663  	})
   664  }
   665  
   666  func TestLostPathResponseFrame(t *testing.T) {
   667  	// "Responses to path validation using PATH_RESPONSE frames are sent just once."
   668  	// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.12
   669  	lostFrameTest(t, func(t *testing.T, pto bool) {
   670  		tc := newTestConn(t, clientSide)
   671  		tc.handshake()
   672  		tc.ignoreFrame(frameTypeAck)
   673  		tc.ignoreFrame(frameTypePing)
   674  
   675  		data := pathChallengeData{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}
   676  		tc.writeFrames(packetType1RTT, debugFramePathChallenge{
   677  			data: data,
   678  		})
   679  		tc.wantFrame("response to PATH_CHALLENGE",
   680  			packetType1RTT, debugFramePathResponse{
   681  				data: data,
   682  			})
   683  
   684  		tc.triggerLossOrPTO(packetType1RTT, pto)
   685  		tc.wantIdle("lost PATH_RESPONSE frame is not retransmitted")
   686  	})
   687  }
   688  
   689  func TestLostHandshakeDoneFrame(t *testing.T) {
   690  	// "The HANDSHAKE_DONE frame MUST be retransmitted until it is acknowledged."
   691  	// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.16
   692  	lostFrameTest(t, func(t *testing.T, pto bool) {
   693  		tc := newTestConn(t, serverSide)
   694  		tc.ignoreFrame(frameTypeAck)
   695  
   696  		tc.writeFrames(packetTypeInitial,
   697  			debugFrameCrypto{
   698  				data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial],
   699  			})
   700  		tc.wantFrame("server sends Initial CRYPTO frame",
   701  			packetTypeInitial, debugFrameCrypto{
   702  				data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial],
   703  			})
   704  		tc.wantFrame("server sends Handshake CRYPTO frame",
   705  			packetTypeHandshake, debugFrameCrypto{
   706  				data: tc.cryptoDataOut[tls.QUICEncryptionLevelHandshake],
   707  			})
   708  		tc.wantFrame("server provides an additional connection ID",
   709  			packetType1RTT, debugFrameNewConnectionID{
   710  				seq:    1,
   711  				connID: testLocalConnID(1),
   712  				token:  testLocalStatelessResetToken(1),
   713  			})
   714  		tc.writeFrames(packetTypeHandshake,
   715  			debugFrameCrypto{
   716  				data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake],
   717  			})
   718  
   719  		tc.wantFrame("server sends HANDSHAKE_DONE after handshake completes",
   720  			packetType1RTT, debugFrameHandshakeDone{})
   721  
   722  		tc.triggerLossOrPTO(packetType1RTT, pto)
   723  		tc.wantFrame("server resends HANDSHAKE_DONE",
   724  			packetType1RTT, debugFrameHandshakeDone{})
   725  	})
   726  }