// Copyright 2025 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package runner

import (
	"slices"
	"strconv"
	"time"
)

func addDTLSReplayTests() {
	for _, vers := range allVersions(dtls) {
		// Test that sequence number replays are detected.
		testCases = append(testCases, testCase{
			protocol: dtls,
			name:     "DTLS-Replay-" + vers.name,
			config: Config{
				MaxVersion: vers.version,
			},
			messageCount: 200,
			replayWrites: true,
		})

		// Test the incoming sequence number skipping by values larger
		// than the retransmit window.
		testCases = append(testCases, testCase{
			protocol: dtls,
			name:     "DTLS-Replay-LargeGaps-" + vers.name,
			config: Config{
				MaxVersion: vers.version,
				Bugs: ProtocolBugs{
					SequenceNumberMapping: func(in uint64) uint64 {
						return in * 1023
					},
				},
			},
			messageCount: 200,
			replayWrites: true,
		})

		// Test the incoming sequence number changing non-monotonically.
		testCases = append(testCases, testCase{
			protocol: dtls,
			name:     "DTLS-Replay-NonMonotonic-" + vers.name,
			config: Config{
				MaxVersion: vers.version,
				Bugs: ProtocolBugs{
					SequenceNumberMapping: func(in uint64) uint64 {
						// This mapping has numbers counting backwards in groups
						// of 256, and then jumping forwards 511 numbers.
						return in ^ 255
					},
				},
			},
			// This messageCount is large enough to make sure that the SequenceNumberMapping
			// will reach the point where it jumps forwards after stepping backwards.
			messageCount: 500,
			replayWrites: true,
		})
	}
}

// timeouts is the default retransmit schedule for BoringSSL. It doubles and
// caps at 60 seconds. On the 13th timeout, it gives up.
var timeouts = []time.Duration{
	400 * time.Millisecond,
	800 * time.Millisecond,
	1600 * time.Millisecond,
	3200 * time.Millisecond,
	6400 * time.Millisecond,
	12800 * time.Millisecond,
	25600 * time.Millisecond,
	51200 * time.Millisecond,
	60 * time.Second,
	60 * time.Second,
	60 * time.Second,
	60 * time.Second,
	60 * time.Second,
}

// shortTimeouts is an alternate set of timeouts which would occur if the
// initial timeout duration was set to 250ms.
var shortTimeouts = []time.Duration{
	250 * time.Millisecond,
	500 * time.Millisecond,
	1 * time.Second,
	2 * time.Second,
	4 * time.Second,
	8 * time.Second,
	16 * time.Second,
	32 * time.Second,
	60 * time.Second,
	60 * time.Second,
	60 * time.Second,
	60 * time.Second,
	60 * time.Second,
}

// dtlsPrevEpochExpiration is how long before the shim releases old epochs. Add
// an extra second to allow the shim to be less precise.
const dtlsPrevEpochExpiration = 4*time.Minute + 1*time.Second

func addDTLSRetransmitTests() {
	for _, shortTimeout := range []bool{false, true} {
		for _, vers := range allVersions(dtls) {
			suffix := "-" + vers.name
			flags := []string{"-async"} // Retransmit tests require async.
			useTimeouts := timeouts
			if shortTimeout {
				suffix += "-Short"
				flags = append(flags, "-initial-timeout-duration-ms", "250")
				useTimeouts = shortTimeouts
			}

			// Testing NewSessionTicket is tricky. First, BoringSSL sends two
			// tickets in a row. These are conceptually separate flights, but we
			// test them as one flight. Second, these tickets are sent
			// concurrently with the runner's first test message. The shim's
			// reply will come in before any retransmit challenges.
			// handleNewSessionTicket corrects for both effects.
			handleNewSessionTicket := func(f ACKFlightFunc) ACKFlightFunc {
				if vers.version < VersionTLS13 {
					return f
				}
				return func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
					// BoringSSL sends two NewSessionTickets in a row.
					if received[0].Type == typeNewSessionTicket && len(received) < 2 {
						c.MergeIntoNextFlight()
						return
					}
					// NewSessionTicket is sent in parallel with the runner's
					// first application data. Consume the shim's reply.
					testMessage := makeTestMessage(0, 32)
					if received[0].Type == typeNewSessionTicket {
						c.ReadAppData(c.InEpoch(), expectedReply(testMessage))
					}
					// Run the test, without any stray messages in the way.
					f(c, prev, received, records)
					// The test loop is expecting a reply to the first message.
					// Prime the shim to send it again.
					if received[0].Type == typeNewSessionTicket {
						c.WriteAppData(c.OutEpoch(), testMessage)
					}
				}
			}

			// In all versions, the sender will retransmit the whole flight if
			// it times out and hears nothing.
			writeFlightBasic := func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
				if len(received) > 0 {
					// Exercise every timeout but the last one (which would fail the
					// connection).
					for _, t := range useTimeouts[:len(useTimeouts)-1] {
						c.ExpectNextTimeout(t)
						c.AdvanceClock(t)
						c.ReadRetransmit()
					}
					c.ExpectNextTimeout(useTimeouts[len(useTimeouts)-1])
				}
				// Finally release the whole flight to the shim.
				c.WriteFlight(next)
			}

			ackFlightBasic := handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
				if vers.version >= VersionTLS13 {
					// In DTLS 1.3, final flights (either handshake or post-handshake)
					// are retransmitted until ACKed. Exercise every timeout but
					// the last one (which would fail the connection).
					for _, t := range useTimeouts[:len(useTimeouts)-1] {
						c.ExpectNextTimeout(t)
						c.AdvanceClock(t)
						c.ReadRetransmit()
					}
					c.ExpectNextTimeout(useTimeouts[len(useTimeouts)-1])
					// Finally ACK the flight.
					c.WriteACK(c.OutEpoch(), records)
					return
				}
				// In DTLS 1.2, the final flight is retransmitted on receipt of
				// the previous flight. Test the peer is willing to retransmit
				// it several times.
				for range 5 {
					c.WriteFlight(prev)
					c.ReadRetransmit()
				}
			})
			testCases = append(testCases, testCase{
				protocol: dtls,
				name:     "DTLS-Retransmit-Client-Basic" + suffix,
				config: Config{
					MaxVersion: vers.version,
					Bugs: ProtocolBugs{
						WriteFlightDTLS: writeFlightBasic,
						ACKFlightDTLS:   ackFlightBasic,
					},
				},
				resumeSession: true,
				flags:         flags,
			})
			testCases = append(testCases, testCase{
				protocol: dtls,
				name:     "DTLS-Retransmit-Client-SetTimeout-Decrease" + suffix,
				config: Config{
					MaxVersion: vers.version,
					Bugs: ProtocolBugs{
						WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
							if len(received) > 0 {
								t := useTimeouts[0]
								tn := useTimeouts[0] / 2
								c.ExpectNextTimeout(t)
								c.SetTimeout(tn)
								c.ExpectNextTimeout(tn)
								c.AdvanceClock(tn)
								c.ReadRetransmit()
								// The timer doubles after each retransmit. Timeout updates respect this,
								// so the effective timeout should be 2 * t.
								c.SetTimeout(t)
								c.ExpectNextTimeout(2 * t)
							}
							// Finally release the whole flight to the shim.
							c.WriteFlight(next)
						},
						ACKFlightDTLS: ackFlightBasic,
					},
				},
				resumeSession: true,
				flags:         flags,
			})
			testCases = append(testCases, testCase{
				protocol: dtls,
				name:     "DTLS-Retransmit-Client-SetTimeout-Fire" + suffix,
				config: Config{
					MaxVersion: vers.version,
					Bugs: ProtocolBugs{
						WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
							if len(received) > 0 {
								t := useTimeouts[0]
								tn := useTimeouts[0] / 2
								c.ExpectNextTimeout(t)
								c.AdvanceClock(tn)
								c.SetTimeout(tn)
								// After rescaling the timer, the running timer immediately fires,
								// so the next timeout should be reported as zero.
								c.ExpectNextTimeout(0)
								// Triggering the shim to handle the timeouts will cause it to
								// retransmit.
								c.AdvanceClock(0)
								c.ReadRetransmit()
								// The next timeout is the rescaled second timeout.
								c.ExpectNextTimeout(useTimeouts[1] / 2)
								c.SetTimeout(t)
							}
							// Finally release the whole flight to the shim.
							c.WriteFlight(next)
						},
						ACKFlightDTLS: ackFlightBasic,
					},
				},
				resumeSession: true,
				flags:         flags,
			})
			testCases = append(testCases, testCase{
				protocol: dtls,
				name:     "DTLS-Retransmit-Client-SetTimeout-Increase" + suffix,
				config: Config{
					MaxVersion: vers.version,
					Bugs: ProtocolBugs{
						WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
							if len(received) > 0 {
								t := useTimeouts[0]
								tn := useTimeouts[0] * 2
								c.ExpectNextTimeout(t)
								c.SetTimeout(tn)
								c.ExpectNextTimeout(tn)
								c.AdvanceClock(t)
								c.AdvanceClock(t)
								c.ReadRetransmit()
								// The timer doubles after each retransmit. Timeout updates respect this,
								// so the effective timeout should be 2 * t.
								c.SetTimeout(t)
								c.ExpectNextTimeout(2 * t)
							}
							// Finally release the whole flight to the shim.
							c.WriteFlight(next)
						},
						ACKFlightDTLS: ackFlightBasic,
					},
				},
				resumeSession: true,
				flags:         flags,
			})
			testCases = append(testCases, testCase{
				protocol: dtls,
				testType: serverTest,
				name:     "DTLS-Retransmit-Server-Basic" + suffix,
				config: Config{
					MaxVersion: vers.version,
					Bugs: ProtocolBugs{
						WriteFlightDTLS: writeFlightBasic,
						ACKFlightDTLS:   ackFlightBasic,
					},
				},
				resumeSession: true,
				flags:         flags,
			})

			if vers.version <= VersionTLS12 {
				// In DTLS 1.2, receiving a part of the next flight should not stop
				// the retransmission timer.
				testCases = append(testCases, testCase{
					protocol: dtls,
					name:     "DTLS-Retransmit-PartialProgress" + suffix,
					config: Config{
						MaxVersion: vers.version,
						Bugs: ProtocolBugs{
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								// Send a portion of the first message. The rest was lost.
								msg := next[0]
								split := len(msg.Data) / 2
								c.WriteFragments([]DTLSFragment{msg.Fragment(0, split)})
								// If we time out, the shim should still retransmit. It knows
								// we received the whole flight, but the shim should use a
								// retransmit to request the runner try again.
								c.AdvanceClock(useTimeouts[0])
								c.ReadRetransmit()
								// "Retransmit" the rest of the flight. The shim should remember
								// the portion that was already sent.
								rest := []DTLSFragment{msg.Fragment(split, len(msg.Data)-split)}
								for _, m := range next[1:] {
									rest = append(rest, m.Fragment(0, len(m.Data)))
								}
								c.WriteFragments(rest)
							},
						},
					},
					flags: flags,
				})
			} else {
				// In DTLS 1.3, receiving a part of the next flight implicitly ACKs
				// the previous flight.
				testCases = append(testCases, testCase{
					testType: serverTest,
					protocol: dtls,
					name:     "DTLS-Retransmit-PartialProgress-Server" + suffix,
					config: Config{
						MaxVersion:    vers.version,
						DefaultCurves: []CurveID{}, // Force HelloRetryRequest.
						Bugs: ProtocolBugs{
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if len(received) == 0 && next[0].Type == typeClientHello {
									// Send the initial ClientHello as-is.
									c.WriteFlight(next)
									return
								}

								// Send a portion of the first message. The rest was lost.
								msg := next[0]
								split := len(msg.Data) / 2
								c.WriteFragments([]DTLSFragment{msg.Fragment(0, split)})
								// After waiting the current timeout, the shim should ACK
								// the partial flight.
								c.ExpectNextTimeout(useTimeouts[0] / 4)
								c.AdvanceClock(useTimeouts[0] / 4)
								c.ReadACK(c.InEpoch())
								// The partial flight is enough to ACK the previous flight.
								// The shim should stop retransmitting and even stop the
								// retransmit timer.
								c.ExpectNoNextTimeout()
								for _, t := range useTimeouts {
									c.AdvanceClock(t)
								}
								// "Retransmit" the rest of the flight. The shim should remember
								// the portion that was already sent.
								rest := []DTLSFragment{msg.Fragment(split, len(msg.Data)-split)}
								for _, m := range next[1:] {
									rest = append(rest, m.Fragment(0, len(m.Data)))
								}
								c.WriteFragments(rest)
							},
						},
					},
					flags: flags,
				})

				// Same as 'DTLS-Retransmit-PartialProgress-Server' but modifying timeout affecting ACKs.
				testCases = append(testCases, testCase{
					testType: serverTest,
					protocol: dtls,
					name:     "DTLS-Retransmit-PartialProgress-Server-SetTimeout" + suffix,
					config: Config{
						MaxVersion:    vers.version,
						DefaultCurves: []CurveID{}, // Force HelloRetryRequest.
						Bugs: ProtocolBugs{
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if len(received) == 0 && next[0].Type == typeClientHello {
									// Send the initial ClientHello as-is.
									c.WriteFlight(next)
									return
								}

								// Send a portion of the first message. The rest was lost.
								// This makes the shim schedule an ACK timer.
								split := len(next[0].Data) / 2
								c.WriteFragments([]DTLSFragment{next[0].Fragment(0, split)})

								// Modify ongoing ACK timer.
								tn := useTimeouts[0] / 2
								c.SetTimeout(tn)

								// The ACK timer should have been rescaled with the update.
								c.ExpectNextTimeout(tn / 4)
								c.AdvanceClock(tn / 4)
								c.ReadACK(c.InEpoch())

								// Restore the timeout and continue the test.
								c.SetTimeout(useTimeouts[0])
								c.WriteFlight(next)
							},
						},
					},
					flags: flags,
				})

				// When the shim is a client, receiving fragments before the version is
				// known does not trigger this behavior.
				testCases = append(testCases, testCase{
					protocol: dtls,
					name:     "DTLS-Retransmit-PartialProgress-Client" + suffix,
					config: Config{
						MaxVersion: vers.version,
						Bugs: ProtocolBugs{
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								msg := next[0]
								if msg.Type != typeServerHello {
									// Post-handshake is tested separately.
									c.WriteFlight(next)
									return
								}
								// Send a portion of the ServerHello. The rest was lost.
								split := len(msg.Data) / 2
								c.WriteFragments([]DTLSFragment{msg.Fragment(0, split)})

								// The shim did not know this was DTLS 1.3, so it still
								// retransmits ClientHello.
								c.ExpectNextTimeout(useTimeouts[0])
								c.AdvanceClock(useTimeouts[0])
								c.ReadRetransmit()

								// Finish the ServerHello. The version is still not known,
								// at the time the ServerHello fragment is processed, This
								// is not as efficient as we could be; we could go back and
								// implicitly ACK once the version is known. But the last
								// byte of ServerHello will almost certainly be in the same
								// packet as EncryptedExtensions, which will trigger the case
								// below.
								c.WriteFragments([]DTLSFragment{msg.Fragment(split, len(msg.Data)-split)})
								c.ExpectNextTimeout(useTimeouts[1])
								c.AdvanceClock(useTimeouts[1])
								c.ReadRetransmit()

								// Send EncryptedExtensions. The shim now knows the version.
								c.WriteFragments([]DTLSFragment{next[1].Fragment(0, len(next[1].Data))})

								// The shim should ACK the partial flight. The shim hasn't
								// gotten to epoch 3 yet, so the ACK will come in epoch 2.
								c.AdvanceClock(useTimeouts[2] / 4)
								c.ReadACK(uint16(encryptionHandshake))

								// This is enough to ACK the previous flight. The shim
								// should stop retransmitting and even stop the timer.
								c.ExpectNoNextTimeout()
								for _, t := range useTimeouts[2:] {
									c.AdvanceClock(t)
								}

								// "Retransmit" the rest of the flight. The shim should remember
								// the portion that was already sent.
								var rest []DTLSFragment
								for _, m := range next[2:] {
									rest = append(rest, m.Fragment(0, len(m.Data)))
								}
								c.WriteFragments(rest)
							},
						},
					},
					flags: flags,
				})

				// Same as 'DTLS-Retransmit-PartialProgress-Client' but updating timeout while ACK timer is set.
				testCases = append(testCases, testCase{
					protocol: dtls,
					name:     "DTLS-Retransmit-PartialProgress-Client-SetTimeout" + suffix,
					config: Config{
						MaxVersion: vers.version,
						Bugs: ProtocolBugs{
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if next[0].Type != typeServerHello {
									// Post-handshake is tested separately.
									c.WriteFlight(next)
									return
								}

								// Send the ServerHello and half of EncryptedExtensions. The rest was lost.
								// This makes the shim schedule an ACK timer. (A partial ServerHello will
								// not schedule an ACK timer because the shim has not learned the version.)
								c.WriteFlight(next[:1])
								split := len(next[1].Data) / 2
								c.WriteFragments([]DTLSFragment{next[1].Fragment(0, split)})

								// Now modify timeout.
								tn := useTimeouts[0] / 2
								c.SetTimeout(tn)

								// The ACK timer should have been rescaled with the update.
								// The shim hasn't gotten to epoch 3 yet, so the ACK will come in epoch 2.
								c.ExpectNextTimeout(tn / 4)
								c.AdvanceClock(tn / 4)
								c.ReadACK(uint16(encryptionHandshake))

								// Restore the timeout and continue the test.
								c.SetTimeout(useTimeouts[0])
								c.WriteFlight(next)
							},
						},
					},
					flags: flags,
				})
			}

			// Test that exceeding the timeout schedule hits a read
			// timeout.
			testCases = append(testCases, testCase{
				protocol: dtls,
				name:     "DTLS-Retransmit-Timeout" + suffix,
				config: Config{
					MaxVersion: vers.version,
					Bugs: ProtocolBugs{
						WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
							for _, t := range useTimeouts[:len(useTimeouts)-1] {
								c.ExpectNextTimeout(t)
								c.AdvanceClock(t)
								c.ReadRetransmit()
							}
							c.ExpectNextTimeout(useTimeouts[len(useTimeouts)-1])
							c.AdvanceClock(useTimeouts[len(useTimeouts)-1])
							// The shim should give up at this point.
						},
					},
				},
				resumeSession: true,
				flags:         flags,
				shouldFail:    true,
				expectedError: ":READ_TIMEOUT_EXPIRED:",
			})

			// Test that timeout handling has a fudge factor, due to API
			// problems.
			testCases = append(testCases, testCase{
				protocol: dtls,
				name:     "DTLS-Retransmit-Fudge" + suffix,
				config: Config{
					MaxVersion: vers.version,
					Bugs: ProtocolBugs{
						WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
							if len(received) > 0 {
								c.ExpectNextTimeout(useTimeouts[0])
								c.AdvanceClock(useTimeouts[0] - 10*time.Millisecond)
								c.ReadRetransmit()
							}
							c.WriteFlight(next)
						},
					},
				},
				resumeSession: true,
				flags:         flags,
			})

			// Test attempting to handle a timeout in multiple steps.
			testCases = append(testCases, testCase{
				protocol: dtls,
				name:     "DTLS-Retransmit-HandleTimeoutInSteps" + suffix,
				config: Config{
					MaxVersion: vers.version,
					Bugs: ProtocolBugs{
						WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
							if len(received) > 0 {
								c.ExpectNextTimeout(useTimeouts[0])
								c.AdvanceClock(0)
								part := useTimeouts[0] / 3
								c.AdvanceClock(part)
								c.AdvanceClock(useTimeouts[0] - part)
								c.ReadRetransmit()
							}
							c.WriteFlight(next)
						},
					},
				},
				resumeSession: true,
				flags:         flags,
			})

			// Test that the shim can retransmit at different MTUs.
			testCases = append(testCases, testCase{
				protocol: dtls,
				name:     "DTLS-Retransmit-ChangeMTU" + suffix,
				config: Config{
					MaxVersion: vers.version,
					// Request a client certificate, so the shim has more to send.
					ClientAuth: RequireAnyClientCert,
					Bugs: ProtocolBugs{
						WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
							for i, mtu := range []int{300, 301, 302, 303, 299, 298, 297} {
								c.SetMTU(mtu)
								c.AdvanceClock(useTimeouts[i])
								c.ReadRetransmit()
							}
							c.WriteFlight(next)
						},
					},
				},
				shimCertificate: &rsaChainCertificate,
				flags:           flags,
			})

			// DTLS 1.3 uses explicit ACKs.
			if vers.version >= VersionTLS13 {
				// The two server flights (HelloRetryRequest and ServerHello..Finished)
				// happen after the shim has learned the version, so they are more
				// straightforward. In these tests, we trigger HelloRetryRequest,
				// and also use ML-KEM with rsaChainCertificate and a limited MTU,
				// to increase the number of records and exercise more complex
				// ACK patterns.

				// After ACKing everything, the shim should stop retransmitting.
				testCases = append(testCases, testCase{
					protocol: dtls,
					testType: serverTest,
					name:     "DTLS-Retransmit-Server-ACKEverything" + suffix,
					config: Config{
						MaxVersion:       vers.version,
						Credential:       &rsaChainCertificate,
						CurvePreferences: []CurveID{CurveX25519MLKEM768},
						DefaultCurves:    []CurveID{}, // Force HelloRetryRequest.
						Bugs: ProtocolBugs{
							// Send smaller packets to exercise more ACK cases.
							MaxPacketLength:          512,
							MaxHandshakeRecordLength: 512,
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if len(received) > 0 {
									ackEpoch := received[len(received)-1].Epoch
									c.ExpectNextTimeout(useTimeouts[0])
									c.WriteACK(ackEpoch, records)
									// After everything is ACKed, the shim should stop the timer
									// and wait for the next flight.
									c.ExpectNoNextTimeout()
									for _, t := range useTimeouts {
										c.AdvanceClock(t)
									}
								}
								c.WriteFlight(next)
							},
							ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
								ackEpoch := received[len(received)-1].Epoch
								c.ExpectNextTimeout(useTimeouts[0])
								c.WriteACK(ackEpoch, records)
								// After everything is ACKed, the shim should stop the timer.
								c.ExpectNoNextTimeout()
								for _, t := range useTimeouts {
									c.AdvanceClock(t)
								}
							}),
							SequenceNumberMapping: func(in uint64) uint64 {
								// Perturb sequence numbers to test that ACKs are sorted.
								return in ^ 63
							},
						},
					},
					shimCertificate: &rsaChainCertificate,
					flags: slices.Concat(flags, []string{
						"-mtu", "512",
						"-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
						// Request a client certificate so the client final flight is
						// larger.
						"-require-any-client-certificate",
					}),
				})

				// ACK packets one by one, in reverse.
				testCases = append(testCases, testCase{
					protocol: dtls,
					testType: serverTest,
					name:     "DTLS-Retransmit-Server-ACKReverse" + suffix,
					config: Config{
						MaxVersion:       vers.version,
						CurvePreferences: []CurveID{CurveX25519MLKEM768},
						DefaultCurves:    []CurveID{}, // Force HelloRetryRequest.
						Bugs: ProtocolBugs{
							MaxPacketLength: 512,
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if len(received) > 0 {
									ackEpoch := received[len(received)-1].Epoch
									for _, t := range useTimeouts[:len(useTimeouts)-1] {
										if len(records) > 0 {
											c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[len(records)-1]})
										}
										c.AdvanceClock(t)
										records = c.ReadRetransmit()
									}
								}
								c.WriteFlight(next)
							},
							ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
								ackEpoch := received[len(received)-1].Epoch
								for _, t := range useTimeouts[:len(useTimeouts)-1] {
									if len(records) > 0 {
										c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[len(records)-1]})
									}
									c.AdvanceClock(t)
									records = c.ReadRetransmit()
								}
							}),
						},
					},
					shimCertificate: &rsaChainCertificate,
					flags:           slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
				})

				// ACK packets one by one, forwards.
				testCases = append(testCases, testCase{
					protocol: dtls,
					testType: serverTest,
					name:     "DTLS-Retransmit-Server-ACKForwards" + suffix,
					config: Config{
						MaxVersion:       vers.version,
						CurvePreferences: []CurveID{CurveX25519MLKEM768},
						DefaultCurves:    []CurveID{}, // Force HelloRetryRequest.
						Bugs: ProtocolBugs{
							MaxPacketLength: 512,
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if len(received) > 0 {
									ackEpoch := received[len(received)-1].Epoch
									for _, t := range useTimeouts[:len(useTimeouts)-1] {
										if len(records) > 0 {
											c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[0]})
										}
										c.AdvanceClock(t)
										records = c.ReadRetransmit()
									}
								}
								c.WriteFlight(next)
							},
							ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
								ackEpoch := received[len(received)-1].Epoch
								for _, t := range useTimeouts[:len(useTimeouts)-1] {
									if len(records) > 0 {
										c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[0]})
									}
									c.AdvanceClock(t)
									records = c.ReadRetransmit()
								}
							}),
						},
					},
					shimCertificate: &rsaChainCertificate,
					flags:           slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
				})

				// ACK 1/3 the packets each time.
				testCases = append(testCases, testCase{
					protocol: dtls,
					testType: serverTest,
					name:     "DTLS-Retransmit-Server-ACKIterate" + suffix,
					config: Config{
						MaxVersion:       vers.version,
						CurvePreferences: []CurveID{CurveX25519MLKEM768},
						DefaultCurves:    []CurveID{}, // Force HelloRetryRequest.
						Bugs: ProtocolBugs{
							MaxPacketLength: 512,
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if len(received) > 0 {
									ackEpoch := received[len(received)-1].Epoch
									for i, t := range useTimeouts[:len(useTimeouts)-1] {
										if len(records) > 0 {
											ack := make([]DTLSRecordNumberInfo, 0, (len(records)+2)/3)
											for i := 0; i < len(records); i += 3 {
												ack = append(ack, records[i])
											}
											c.WriteACK(ackEpoch, ack)
										}
										// Change the MTU every iteration, to make the fragment
										// patterns more complex.
										c.SetMTU(512 + i)
										c.AdvanceClock(t)
										records = c.ReadRetransmit()
									}
								}
								c.WriteFlight(next)
							},
							ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
								ackEpoch := received[len(received)-1].Epoch
								for _, t := range useTimeouts[:len(useTimeouts)-1] {
									if len(records) > 0 {
										c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[0]})
									}
									c.AdvanceClock(t)
									records = c.ReadRetransmit()
								}
							}),
						},
					},
					shimCertificate: &rsaChainCertificate,
					flags:           slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
				})

				// ACKing packets that have already been ACKed is a no-op.
				testCases = append(testCases, testCase{
					protocol: dtls,
					testType: serverTest,
					name:     "DTLS-Retransmit-Server-ACKDuplicate" + suffix,
					config: Config{
						MaxVersion: vers.version,
						Bugs: ProtocolBugs{
							SendHelloRetryRequestCookie: []byte("cookie"),
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if len(received) > 0 {
									ackEpoch := received[len(received)-1].Epoch
									// Keep ACKing the same record over and over.
									c.WriteACK(ackEpoch, records[:1])
									c.AdvanceClock(useTimeouts[0])
									c.ReadRetransmit()
									c.WriteACK(ackEpoch, records[:1])
									c.AdvanceClock(useTimeouts[1])
									c.ReadRetransmit()
								}
								c.WriteFlight(next)
							},
							ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
								ackEpoch := received[len(received)-1].Epoch
								// Keep ACKing the same record over and over.
								c.WriteACK(ackEpoch, records[:1])
								c.AdvanceClock(useTimeouts[0])
								c.ReadRetransmit()
								c.WriteACK(ackEpoch, records[:1])
								c.AdvanceClock(useTimeouts[1])
								c.ReadRetransmit()
								// ACK everything to clear the timer.
								c.WriteACK(ackEpoch, records)
							}),
						},
					},
					flags: flags,
				})

				// When ACKing ServerHello..Finished, the ServerHello might be
				// ACKed at epoch 0 or epoch 2, depending on how far the client
				// received. Test that epoch 0 is allowed by ACKing each packet
				// at the record it was received.
				testCases = append(testCases, testCase{
					protocol: dtls,
					testType: serverTest,
					name:     "DTLS-Retransmit-Server-ACKMatchingEpoch" + suffix,
					config: Config{
						MaxVersion: vers.version,
						Bugs: ProtocolBugs{
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if len(received) > 0 {
									for _, t := range useTimeouts[:len(useTimeouts)-1] {
										if len(records) > 0 {
											c.WriteACK(uint16(records[0].Epoch), []DTLSRecordNumberInfo{records[0]})
										}
										c.AdvanceClock(t)
										records = c.ReadRetransmit()
									}
								}
								c.WriteFlight(next)
							},
						},
					},
					flags: flags,
				})

				// However, records in the handshake may not be ACKed at lower
				// epoch than they were received.
				testCases = append(testCases, testCase{
					protocol: dtls,
					testType: serverTest,
					name:     "DTLS-Retransmit-Server-ACKBadEpoch" + suffix,
					config: Config{
						MaxVersion: vers.version,
						Bugs: ProtocolBugs{
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if len(received) == 0 {
									// Send the ClientHello.
									c.WriteFlight(next)
								} else {
									// Try to ACK ServerHello..Finished at epoch 0. The shim should reject this.
									c.WriteACK(0, records)
								}
							},
						},
					},
					flags:         flags,
					shouldFail:    true,
					expectedError: ":DECODE_ERROR:",
				})

				// The bad epoch check should notice when the epoch number
				// would overflow 2^16.
				testCases = append(testCases, testCase{
					protocol: dtls,
					testType: serverTest,
					name:     "DTLS-Retransmit-Server-ACKEpochOverflow" + suffix,
					config: Config{
						MaxVersion: vers.version,
						Bugs: ProtocolBugs{
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if len(received) == 0 {
									// Send the ClientHello.
									c.WriteFlight(next)
								} else {
									r := records[0]
									r.Epoch += 1 << 63
									c.WriteACK(0, []DTLSRecordNumberInfo{r})
								}
							},
						},
					},
					flags:         flags,
					shouldFail:    true,
					expectedError: ":DECODE_ERROR:",
				})

				// ACK some records from the first transmission, trigger a
				// retransmit, but then ACK the rest of the first transmission.
				testCases = append(testCases, testCase{
					protocol: dtls,
					testType: serverTest,
					name:     "DTLS-Retransmit-Server-ACKOldRecords" + suffix,
					config: Config{
						MaxVersion:       vers.version,
						CurvePreferences: []CurveID{CurveX25519MLKEM768},
						Bugs: ProtocolBugs{
							MaxPacketLength: 512,
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if len(received) > 0 {
									ackEpoch := received[len(received)-1].Epoch
									c.WriteACK(ackEpoch, records[len(records)/2:])
									c.AdvanceClock(useTimeouts[0])
									c.ReadRetransmit()
									c.WriteACK(ackEpoch, records[:len(records)/2])
									// Everything should be ACKed now. The shim should not
									// retransmit anything.
									c.AdvanceClock(useTimeouts[1])
									c.AdvanceClock(useTimeouts[2])
									c.AdvanceClock(useTimeouts[3])
								}
								c.WriteFlight(next)
							},
						},
					},
					flags: slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
				})

				// If the shim sends too many records, it will eventually forget them.
				testCases = append(testCases, testCase{
					protocol: dtls,
					testType: serverTest,
					name:     "DTLS-Retransmit-Server-ACKForgottenRecords" + suffix,
					config: Config{
						MaxVersion:       vers.version,
						CurvePreferences: []CurveID{CurveX25519MLKEM768},
						Bugs: ProtocolBugs{
							MaxPacketLength: 256,
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if len(received) > 0 {
									// Make the peer retransmit many times, with a small MTU.
									for _, t := range useTimeouts[:len(useTimeouts)-2] {
										c.AdvanceClock(t)
										c.ReadRetransmit()
									}
									// ACK the first record the shim ever sent. It will have
									// fallen off the queue by now, so it is expected to not
									// impact the shim's retransmissions.
									c.WriteACK(c.OutEpoch(), []DTLSRecordNumberInfo{{DTLSRecordNumber: records[0].DTLSRecordNumber}})
									c.AdvanceClock(useTimeouts[len(useTimeouts)-2])
									c.ReadRetransmit()
								}
								c.WriteFlight(next)
							},
						},
					},
					flags: slices.Concat(flags, []string{"-mtu", "256", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
				})

				// The shim should ignore ACKs for a previous flight, and not get its
				// internal state confused.
				testCases = append(testCases, testCase{
					protocol: dtls,
					testType: serverTest,
					name:     "DTLS-Retransmit-Server-ACKPreviousFlight" + suffix,
					config: Config{
						MaxVersion:    vers.version,
						DefaultCurves: []CurveID{}, // Force a HelloRetryRequest.
						Bugs: ProtocolBugs{
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if next[len(next)-1].Type == typeFinished {
									// We are now sending client Finished, in response
									// to the shim's ServerHello. ACK the shim's first
									// record, which would have been part of
									// HelloRetryRequest. This should not impact retransmit.
									c.WriteACK(c.OutEpoch(), []DTLSRecordNumberInfo{{DTLSRecordNumber: DTLSRecordNumber{Epoch: 0, Sequence: 0}}})
									c.AdvanceClock(useTimeouts[0])
									c.ReadRetransmit()
								}
								c.WriteFlight(next)
							},
						},
					},
					flags: flags,
				})

				// Records that contain a mix of discarded and processed fragments should
				// not be ACKed.
				testCases = append(testCases, testCase{
					protocol: dtls,
					testType: serverTest,
					name:     "DTLS-Retransmit-Server-DoNotACKDiscardedFragments" + suffix,
					config: Config{
						MaxVersion:    vers.version,
						DefaultCurves: []CurveID{}, // Force a HelloRetryRequest.
						Bugs: ProtocolBugs{
							PackHandshakeFragments: 4096,
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								// Send the flight, but combine every fragment with a far future
								// fragment, which the shim will discard. During the handshake,
								// the shim has enough information to reject this entirely, but
								// that would require coordinating with the handshake state
								// machine. Instead, BoringSSL discards the fragment and skips
								// ACKing the packet.
								//
								// runner implicitly tests that the shim ACKs the Finished flight
								// (or, in case, that it is does not), so this exercises the final
								// ACK.
								for _, msg := range next {
									shouldDiscard := DTLSFragment{Epoch: msg.Epoch, Sequence: 1000, ShouldDiscard: true}
									c.WriteFragments([]DTLSFragment{shouldDiscard, msg.Fragment(0, len(msg.Data))})
									// The shim has nothing to ACK and thus no ACK timer (which
									// would be 1/4 of this value).
									c.ExpectNextTimeout(useTimeouts[0])
								}
							},
						},
					},
					flags: flags,
				})

				// The server must continue to ACK the Finished flight even after
				// receiving application data from the client.
				testCases = append(testCases, testCase{
					protocol: dtls,
					testType: serverTest,
					name:     "DTLS-Retransmit-Server-ACKFinishedAfterAppData" + suffix,
					config: Config{
						MaxVersion: vers.version,
						Bugs: ProtocolBugs{
							// WriteFlightDTLS will handle consuming ACKs.
							SkipImplicitACKRead: true,
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if next[len(next)-1].Type != typeFinished {
									c.WriteFlight(next)
									return
								}

								// Write Finished. The shim should ACK it immediately.
								c.WriteFlight(next)
								c.ReadACK(c.InEpoch())

								// Exchange some application data.
								msg := []byte("hello")
								c.WriteAppData(c.OutEpoch(), msg)
								c.ReadAppData(c.InEpoch(), expectedReply(msg))

								// Act as if the ACK was dropped and retransmit Finished.
								// The shim should process the retransmit from epoch 2 and
								// ACK, although it has already received data at epoch 3.
								c.WriteFlight(next)
								ackTimeout := useTimeouts[0] / 4
								c.AdvanceClock(ackTimeout)
								c.ReadACK(c.InEpoch())

								// Partially retransmit Finished. The shim should continue
								// to ACK.
								c.WriteFragments([]DTLSFragment{next[0].Fragment(0, 1)})
								c.WriteFragments([]DTLSFragment{next[0].Fragment(1, 1)})
								c.AdvanceClock(ackTimeout)
								c.ReadACK(c.InEpoch())

								// Eventually, the shim assumes we have received the ACK
								// and drops epoch 2. Retransmits now go unanswered.
								c.AdvanceClock(dtlsPrevEpochExpiration)
								c.WriteFlight(next)
							},
						},
					},
					// Disable tickets on the shim to avoid NewSessionTicket
					// interfering with the test callback.
					flags: slices.Concat(flags, []string{"-no-ticket"}),
				})

				// As a client, the shim must tolerate ACKs in response to its
				// initial ClientHello, but it will not process them because the
				// version is not yet known. The second ClientHello, in response
				// to HelloRetryRequest, however, is ACKed.
				//
				// The shim must additionally process ACKs and retransmit its
				// Finished flight, possibly interleaved with application data.
				// (The server may send half-RTT data without Finished.)
				testCases = append(testCases, testCase{
					protocol: dtls,
					name:     "DTLS-Retransmit-Client" + suffix,
					config: Config{
						MaxVersion: vers.version,
						// Require a client certificate, so the Finished flight
						// is large.
						ClientAuth: RequireAnyClientCert,
						Bugs: ProtocolBugs{
							SendHelloRetryRequestCookie: []byte("cookie"), // Send HelloRetryRequest
							MaxPacketLength:             512,
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if len(received) == 0 || received[0].Type != typeClientHello {
									// We test post-handshake flights separately.
									c.WriteFlight(next)
									return
								}

								// This is either HelloRetryRequest in response to ClientHello1,
								// or ServerHello..Finished in response to ClientHello2.
								first := records[0]
								if len(prev) == 0 {
									// This is HelloRetryRequest in response to ClientHello1. The client
									// will accept the ACK, but it will ignore it. Do not expect
									// retransmits to be impacted.
									first.MessageStartSequence = 0
									first.MessageStartOffset = 0
									first.MessageEndSequence = 0
									first.MessageEndOffset = 0
								}
								c.WriteACK(0, []DTLSRecordNumberInfo{first})
								c.AdvanceClock(useTimeouts[0])
								c.ReadRetransmit()
								c.WriteFlight(next)
							},
							ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
								// The shim will process application data without an ACK.
								msg := []byte("hello")
								c.WriteAppData(c.OutEpoch(), msg)
								c.ReadAppData(c.InEpoch(), expectedReply(msg))

								// After a timeout, the shim will retransmit Finished.
								c.AdvanceClock(useTimeouts[0])
								c.ReadRetransmit()

								// Application data still flows.
								c.WriteAppData(c.OutEpoch(), msg)
								c.ReadAppData(c.InEpoch(), expectedReply(msg))

								// ACK part of the flight and check that retransmits
								// are updated.
								c.WriteACK(c.OutEpoch(), records[len(records)/3:2*len(records)/3])
								c.AdvanceClock(useTimeouts[1])
								records = c.ReadRetransmit()

								// ACK the rest. Retransmits should stop.
								c.WriteACK(c.OutEpoch(), records)
								for _, t := range useTimeouts[2:] {
									c.AdvanceClock(t)
								}
							},
						},
					},
					shimCertificate: &rsaChainCertificate,
					flags:           slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}),
				})

				// If the client never receives an ACK for the Finished flight, it
				// is eventually fatal.
				testCases = append(testCases, testCase{
					protocol: dtls,
					name:     "DTLS-Retransmit-Client-FinishedTimeout" + suffix,
					config: Config{
						MaxVersion: vers.version,
						Bugs: ProtocolBugs{
							ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
								for _, t := range useTimeouts[:len(useTimeouts)-1] {
									c.AdvanceClock(t)
									c.ReadRetransmit()
								}
								c.AdvanceClock(useTimeouts[len(useTimeouts)-1])
							},
						},
					},
					flags:         flags,
					shouldFail:    true,
					expectedError: ":READ_TIMEOUT_EXPIRED:",
				})

				// Neither post-handshake messages nor application data implicitly
				// ACK the Finished flight. The server may have sent either in
				// half-RTT data. Test that the client continues to retransmit
				// despite this.
				testCases = append(testCases, testCase{
					protocol: dtls,
					name:     "DTLS-Retransmit-Client-NoImplictACKFinished" + suffix,
					config: Config{
						MaxVersion: vers.version,
						Bugs: ProtocolBugs{
							ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
								// Merge the Finished flight into the NewSessionTicket.
								c.MergeIntoNextFlight()
							},
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if next[0].Type != typeNewSessionTicket {
									c.WriteFlight(next)
									return
								}
								if len(received) == 0 || received[0].Type != typeFinished {
									panic("Finished should be merged with NewSessionTicket")
								}
								// Merge NewSessionTicket into the KeyUpdate.
								if next[len(next)-1].Type != typeKeyUpdate {
									c.MergeIntoNextFlight()
									return
								}

								// Write NewSessionTicket and the KeyUpdate and
								// read the ACK.
								c.WriteFlight(next)
								ackTimeout := useTimeouts[0] / 4
								c.AdvanceClock(ackTimeout)
								c.ReadACK(c.InEpoch())

								// The retransmit timer is still running.
								c.AdvanceClock(useTimeouts[0] - ackTimeout)
								c.ReadRetransmit()

								// Application data can flow at the old epoch.
								msg := []byte("test")
								c.WriteAppData(c.OutEpoch()-1, msg)
								c.ReadAppData(c.InEpoch(), expectedReply(msg))

								// The retransmit timer is still running.
								c.AdvanceClock(useTimeouts[1])
								c.ReadRetransmit()

								// Advance the shim to the next epoch.
								c.WriteAppData(c.OutEpoch(), msg)
								c.ReadAppData(c.InEpoch(), expectedReply(msg))

								// The retransmit timer is still running. The shim
								// actually could implicitly ACK at this point, but
								// RFC 9147 does not list this as an implicit ACK.
								c.AdvanceClock(useTimeouts[2])
								c.ReadRetransmit()

								// Finally ACK the final flight. Now the shim will
								// stop the timer.
								c.WriteACK(c.OutEpoch(), records)
								c.ExpectNoNextTimeout()
							},
						},
					},
					sendKeyUpdates:   1,
					keyUpdateRequest: keyUpdateNotRequested,
					flags:            flags,
				})

				// If the server never receives an ACK for NewSessionTicket, it
				// is eventually fatal.
				testCases = append(testCases, testCase{
					testType: serverTest,
					protocol: dtls,
					name:     "DTLS-Retransmit-Server-NewSessionTicketTimeout" + suffix,
					config: Config{
						MaxVersion: vers.version,
						Bugs: ProtocolBugs{
							ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
								if received[0].Type != typeNewSessionTicket {
									c.WriteACK(c.OutEpoch(), records)
									return
								}
								// Time the peer out.
								for _, t := range useTimeouts[:len(useTimeouts)-1] {
									c.AdvanceClock(t)
									c.ReadRetransmit()
								}
								c.AdvanceClock(useTimeouts[len(useTimeouts)-1])
							}),
						},
					},
					flags:         flags,
					shouldFail:    true,
					expectedError: ":READ_TIMEOUT_EXPIRED:",
				})

				// If generating the reply to a flight takes time (generating a
				// CertificateVerify for a client certificate), the shim should
				// send an ACK.
				testCases = append(testCases, testCase{
					protocol: dtls,
					name:     "DTLS-Retransmit-SlowReplyGeneration" + suffix,
					config: Config{
						MaxVersion: vers.version,
						ClientAuth: RequireAnyClientCert,
						Bugs: ProtocolBugs{
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								c.WriteFlight(next)
								if next[0].Type == typeServerHello {
									// The shim will reply with Certificate..Finished, but
									// take time to do so. In that time, it should schedule
									// an ACK so the runner knows not to retransmit.
									c.ReadACK(c.InEpoch())
								}
							},
						},
					},
					shimCertificate: &rsaCertificate,
					// Simulate it taking time to generate the reply.
					flags: slices.Concat(flags, []string{"-private-key-delay-ms", strconv.Itoa(int(useTimeouts[0].Milliseconds()))}),
				})

				// BoringSSL's ACK policy may schedule both retransmit and ACK
				// timers in parallel.
				//
				// TODO(crbug.com/42290594): This is only possible during the
				// handshake because we're willing to ACK old flights without
				// trying to distinguish these cases. However, post-handshake
				// messages will exercise this, so that may be a better version
				// of this test. In-handshake, it's kind of a waste to ACK this,
				// so maybe we should stop.
				testCases = append(testCases, testCase{
					protocol: dtls,
					name:     "DTLS-Retransmit-BothTimers" + suffix,
					config: Config{
						MaxVersion: vers.version,
						Bugs: ProtocolBugs{
							// Arrange for there to be two server flights.
							SendHelloRetryRequestCookie: []byte("cookie"),
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if next[0].Sequence == 0 || next[0].Type != typeServerHello {
									// Send the first flight (HelloRetryRequest) as-is,
									// as well as any post-handshake flights.
									c.WriteFlight(next)
									return
								}

								// The shim just send the ClientHello2 and is
								// waiting for ServerHello..Finished. If it hears
								// nothing, it will retransmit ClientHello2 on the
								// assumption the packet was lost.
								c.ExpectNextTimeout(useTimeouts[0])

								// Retransmit a portion of HelloRetryRequest.
								c.WriteFragments([]DTLSFragment{prev[0].Fragment(0, 1)})

								// The shim does not actually need to ACK this,
								// but BoringSSL does. Now both timers are active.
								// Fire the first...
								c.ExpectNextTimeout(useTimeouts[0] / 4)
								c.AdvanceClock(useTimeouts[0] / 4)
								c.ReadACK(0)

								// ...followed by the second.
								c.ExpectNextTimeout(3 * useTimeouts[0] / 4)
								c.AdvanceClock(3 * useTimeouts[0] / 4)
								c.ReadRetransmit()

								// The shim is now set for the next retransmit.
								c.ExpectNextTimeout(useTimeouts[1])

								// Start the ACK timer again.
								c.WriteFragments([]DTLSFragment{prev[0].Fragment(0, 1)})
								c.ExpectNextTimeout(useTimeouts[1] / 4)

								// Expire both timers at once.
								c.AdvanceClock(useTimeouts[1])
								c.ReadACK(0)
								c.ReadRetransmit()

								c.WriteFlight(next)
							},
						},
					},
					flags: flags,
				})

				testCases = append(testCases, testCase{
					protocol: dtls,
					name:     "DTLS-Retransmit-Client-ACKPostHandshake" + suffix,
					config: Config{
						MaxVersion: vers.version,
						Bugs: ProtocolBugs{
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if next[0].Type != typeNewSessionTicket {
									c.WriteFlight(next)
									return
								}

								// The test should try to send two NewSessionTickets in a row.
								if len(next) != 2 {
									panic("unexpected message count")
								}

								// Send part of first ticket post-handshake message.
								first0, second0 := next[0].Split(len(next[0].Data) / 2)
								first1, second1 := next[1].Split(len(next[1].Data) / 2)
								c.WriteFragments([]DTLSFragment{first0})

								// The shim should ACK on a timer.
								c.ExpectNextTimeout(useTimeouts[0] / 4)
								c.AdvanceClock(useTimeouts[0] / 4)
								c.ReadACK(c.InEpoch())

								// The shim is just waiting for us to retransmit.
								c.ExpectNoNextTimeout()

								// Send some more fragments.
								c.WriteFragments([]DTLSFragment{first0, second1})

								// The shim should ACK, again on a timer.
								c.ExpectNextTimeout(useTimeouts[0] / 4)
								c.AdvanceClock(useTimeouts[0] / 4)
								c.ReadACK(c.InEpoch())
								c.ExpectNoNextTimeout()

								// Finish up both messages. We implicitly test if shim
								// processed these messages by checking that it returned a new
								// session.
								c.WriteFragments([]DTLSFragment{first1, second0})

								// The shim should ACK again, once the timer expires.
								//
								// TODO(crbug.com/42290594): Should the shim ACK immediately?
								// Otherwise KeyUpdates are delayed, which will complicated
								// downstream testing.
								c.ExpectNextTimeout(useTimeouts[0] / 4)
								c.AdvanceClock(useTimeouts[0] / 4)
								c.ReadACK(c.InEpoch())
								c.ExpectNoNextTimeout()
							},
						},
					},
					flags: flags,
				})

				testCases = append(testCases, testCase{
					protocol: dtls,
					name:     "DTLS-Retransmit-Client-ACKPostHandshakeTwice" + suffix,
					config: Config{
						MaxVersion: vers.version,
						Bugs: ProtocolBugs{
							WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
								if next[0].Type != typeNewSessionTicket {
									c.WriteFlight(next)
									return
								}

								// The test should try to send two NewSessionTickets in a row.
								if len(next) != 2 {
									panic("unexpected message count")
								}

								// Send the flight. The shim should ACK it.
								c.WriteFlight(next)
								c.AdvanceClock(useTimeouts[0] / 4)
								c.ReadACK(c.InEpoch())
								c.ExpectNoNextTimeout()

								// Retransmit the flight, as if we lost the ACK. The shim should
								// ACK again.
								c.WriteFlight(next)
								c.AdvanceClock(useTimeouts[0] / 4)
								c.ReadACK(c.InEpoch())
								c.ExpectNoNextTimeout()
							},
						},
					},
					flags: flags,
				})
			}
		}
	}

	// Test that the final Finished retransmitting isn't
	// duplicated if the peer badly fragments everything.
	testCases = append(testCases, testCase{
		testType: serverTest,
		protocol: dtls,
		name:     "DTLS-RetransmitFinished-Fragmented",
		config: Config{
			MaxVersion: VersionTLS12,
			Bugs: ProtocolBugs{
				MaxHandshakeRecordLength: 2,
				ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
					c.WriteFlight(prev)
					c.ReadRetransmit()
				},
			},
		},
		flags: []string{"-async"},
	})

	// If the shim sends the last Finished (server full or client resume
	// handshakes), it must retransmit that Finished when it sees a
	// post-handshake penultimate Finished from the runner. The above tests
	// cover this. Conversely, if the shim sends the penultimate Finished
	// (client full or server resume), test that it does not retransmit.
	testCases = append(testCases, testCase{
		protocol: dtls,
		testType: clientTest,
		name:     "DTLS-StrayRetransmitFinished-ClientFull",
		config: Config{
			MaxVersion: VersionTLS12,
			Bugs: ProtocolBugs{
				WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
					c.WriteFlight(next)
					for _, msg := range next {
						if msg.Type == typeFinished {
							c.WriteFlight([]DTLSMessage{msg})
						}
					}
				},
			},
		},
	})
	testCases = append(testCases, testCase{
		protocol: dtls,
		testType: serverTest,
		name:     "DTLS-StrayRetransmitFinished-ServerResume",
		config: Config{
			MaxVersion: VersionTLS12,
		},
		resumeConfig: &Config{
			MaxVersion: VersionTLS12,
			Bugs: ProtocolBugs{
				WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
					c.WriteFlight(next)
					for _, msg := range next {
						if msg.Type == typeFinished {
							c.WriteFlight([]DTLSMessage{msg})
						}
					}
				},
			},
		},
		resumeSession: true,
	})
}

func addDTLSReorderTests() {
	for _, vers := range allVersions(dtls) {
		testCases = append(testCases, testCase{
			protocol: dtls,
			name:     "ReorderHandshakeFragments-Small-DTLS-" + vers.name,
			config: Config{
				MaxVersion: vers.version,
				Bugs: ProtocolBugs{
					ReorderHandshakeFragments: true,
					// Small enough that every handshake message is
					// fragmented.
					MaxHandshakeRecordLength: 2,
				},
			},
		})
		testCases = append(testCases, testCase{
			protocol: dtls,
			name:     "ReorderHandshakeFragments-Large-DTLS-" + vers.name,
			config: Config{
				MaxVersion: vers.version,
				Bugs: ProtocolBugs{
					ReorderHandshakeFragments: true,
					// Large enough that no handshake message is
					// fragmented.
					MaxHandshakeRecordLength: 2048,
				},
			},
		})
		testCases = append(testCases, testCase{
			protocol: dtls,
			name:     "MixCompleteMessageWithFragments-DTLS-" + vers.name,
			config: Config{
				MaxVersion: vers.version,
				Bugs: ProtocolBugs{
					ReorderHandshakeFragments:       true,
					MixCompleteMessageWithFragments: true,
					MaxHandshakeRecordLength:        2,
				},
			},
		})
	}
}
