blob: be4f5fb2c86e77fd1060b48e5f8341de4cdde010 [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 (
"crypto/tls"
"testing"
)
// Frames may be retransmitted either when the packet containing the frame is lost, or on PTO.
// lostFrameTest runs a test in both configurations.
func lostFrameTest(t *testing.T, f func(t *testing.T, pto bool)) {
t.Run("lost", func(t *testing.T) {
f(t, false)
})
t.Run("pto", func(t *testing.T) {
f(t, true)
})
}
// triggerLossOrPTO causes the conn to declare the last sent packet lost,
// or advances to the PTO timer.
func (tc *testConn) triggerLossOrPTO(ptype packetType, pto bool) {
tc.t.Helper()
if pto {
if !tc.conn.loss.ptoTimerArmed {
tc.t.Fatalf("PTO timer not armed, expected it to be")
}
tc.advanceTo(tc.conn.loss.timer)
return
}
defer func(ignoreFrames map[byte]bool) {
tc.ignoreFrames = ignoreFrames
}(tc.ignoreFrames)
tc.ignoreFrames = map[byte]bool{
frameTypeAck: true,
frameTypePadding: true,
}
// Send three packets containing PINGs, and then respond with an ACK for the
// last one. This puts the last packet before the PINGs outside the packet
// reordering threshold, and it will be declared lost.
const lossThreshold = 3
var num packetNumber
for i := 0; i < lossThreshold; i++ {
tc.conn.ping(spaceForPacketType(ptype))
d := tc.readDatagram()
if d == nil {
tc.t.Fatalf("conn is idle; want PING frame")
}
if d.packets[0].ptype != ptype {
tc.t.Fatalf("conn sent %v packet; want %v", d.packets[0].ptype, ptype)
}
num = d.packets[0].num
}
tc.writeFrames(ptype, debugFrameAck{
ranges: []i64range[packetNumber]{
{num, num + 1},
},
})
}
func TestLostCRYPTOFrame(t *testing.T) {
// "Data sent in CRYPTO frames is retransmitted [...] until all data has been acknowledged."
// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.1
lostFrameTest(t, func(t *testing.T, pto bool) {
tc := newTestConn(t, clientSide)
tc.ignoreFrame(frameTypeAck)
tc.wantFrame("client sends Initial CRYPTO frame",
packetTypeInitial, debugFrameCrypto{
data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial],
})
tc.triggerLossOrPTO(packetTypeInitial, pto)
tc.wantFrame("client resends Initial CRYPTO frame",
packetTypeInitial, debugFrameCrypto{
data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial],
})
tc.writeFrames(packetTypeInitial,
debugFrameCrypto{
data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial],
})
tc.writeFrames(packetTypeHandshake,
debugFrameCrypto{
data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake],
})
tc.wantFrame("client sends Handshake CRYPTO frame",
packetTypeHandshake, debugFrameCrypto{
data: tc.cryptoDataOut[tls.QUICEncryptionLevelHandshake],
})
tc.triggerLossOrPTO(packetTypeHandshake, pto)
tc.wantFrame("client resends Handshake CRYPTO frame",
packetTypeHandshake, debugFrameCrypto{
data: tc.cryptoDataOut[tls.QUICEncryptionLevelHandshake],
})
})
}
func TestLostHandshakeDoneFrame(t *testing.T) {
// "The HANDSHAKE_DONE frame MUST be retransmitted until it is acknowledged."
// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.16
lostFrameTest(t, func(t *testing.T, pto bool) {
tc := newTestConn(t, serverSide)
tc.ignoreFrame(frameTypeAck)
tc.writeFrames(packetTypeInitial,
debugFrameCrypto{
data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial],
})
tc.wantFrame("server sends Initial CRYPTO frame",
packetTypeInitial, debugFrameCrypto{
data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial],
})
tc.wantFrame("server sends Handshake CRYPTO frame",
packetTypeHandshake, debugFrameCrypto{
data: tc.cryptoDataOut[tls.QUICEncryptionLevelHandshake],
})
tc.writeFrames(packetTypeHandshake,
debugFrameCrypto{
data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake],
})
tc.wantFrame("server sends HANDSHAKE_DONE after handshake completes",
packetType1RTT, debugFrameHandshakeDone{})
tc.wantFrame("server sends session ticket in CRYPTO frame",
packetType1RTT, debugFrameCrypto{
data: tc.cryptoDataOut[tls.QUICEncryptionLevelApplication],
})
tc.triggerLossOrPTO(packetType1RTT, pto)
tc.wantFrame("server resends HANDSHAKE_DONE",
packetType1RTT, debugFrameHandshakeDone{})
tc.wantFrame("server resends session ticket",
packetType1RTT, debugFrameCrypto{
data: tc.cryptoDataOut[tls.QUICEncryptionLevelApplication],
})
})
}