Run extension tests at all protocols.

Change-Id: Ied0ef856de3a1ae2c65b10645fbce614726f4e20
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/44989
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index f3847d6..1ac7da2 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -6550,1771 +6550,1854 @@
 }
 
 func addExtensionTests() {
-	// TODO(davidben): Extensions, where applicable, all move their server
-	// halves to EncryptedExtensions in TLS 1.3. Duplicate each of these
-	// tests for both. Also test interaction with 0-RTT when implemented.
-
 	// Repeat extensions tests at all versions.
-	for _, ver := range tlsVersions {
-		// Test that duplicate extensions are rejected.
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "DuplicateExtensionClient-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				Bugs: ProtocolBugs{
-					DuplicateExtension: true,
-				},
-			},
-			shouldFail:         true,
-			expectedLocalError: "remote error: error decoding message",
-		})
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "DuplicateExtensionServer-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				Bugs: ProtocolBugs{
-					DuplicateExtension: true,
-				},
-			},
-			shouldFail:         true,
-			expectedLocalError: "remote error: error decoding message",
-		})
+	for _, protocol := range []protocol{tls, dtls, quic} {
+		for _, ver := range allVersions(protocol) {
+			suffix := fmt.Sprintf("%s-%s", protocol.String(), ver.name)
 
-		// Test SNI.
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "ServerNameExtensionClient-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				Bugs: ProtocolBugs{
-					ExpectServerName: "example.com",
-				},
-			},
-			flags: []string{"-host-name", "example.com"},
-		})
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "ServerNameExtensionClientMismatch-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				Bugs: ProtocolBugs{
-					ExpectServerName: "mismatch.com",
-				},
-			},
-			flags:              []string{"-host-name", "example.com"},
-			shouldFail:         true,
-			expectedLocalError: "tls: unexpected server name",
-		})
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "ServerNameExtensionClientMissing-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				Bugs: ProtocolBugs{
-					ExpectServerName: "missing.com",
-				},
-			},
-			shouldFail:         true,
-			expectedLocalError: "tls: unexpected server name",
-		})
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "TolerateServerNameAck-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				Bugs: ProtocolBugs{
-					SendServerNameAck: true,
-				},
-			},
-			flags:         []string{"-host-name", "example.com"},
-			resumeSession: true,
-		})
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "UnsolicitedServerNameAck-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				Bugs: ProtocolBugs{
-					SendServerNameAck: true,
-				},
-			},
-			shouldFail:         true,
-			expectedError:      ":UNEXPECTED_EXTENSION:",
-			expectedLocalError: "remote error: unsupported extension",
-		})
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "ServerNameExtensionServer-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				ServerName: "example.com",
-			},
-			flags:         []string{"-expect-server-name", "example.com"},
-			resumeSession: true,
-		})
-
-		// Test ALPN.
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "ALPNClient-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				NextProtos: []string{"foo"},
-			},
-			flags: []string{
-				"-advertise-alpn", "\x03foo\x03bar\x03baz",
-				"-expect-alpn", "foo",
-			},
-			expectations: connectionExpectations{
-				nextProto:     "foo",
-				nextProtoType: alpn,
-			},
-			resumeSession: true,
-		})
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "ALPNClient-RejectUnknown-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				Bugs: ProtocolBugs{
-					SendALPN: "baz",
-				},
-			},
-			flags: []string{
-				"-advertise-alpn", "\x03foo\x03bar",
-			},
-			shouldFail:         true,
-			expectedError:      ":INVALID_ALPN_PROTOCOL:",
-			expectedLocalError: "remote error: illegal parameter",
-		})
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "ALPNClient-AllowUnknown-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				Bugs: ProtocolBugs{
-					SendALPN: "baz",
-				},
-			},
-			flags: []string{
-				"-advertise-alpn", "\x03foo\x03bar",
-				"-allow-unknown-alpn-protos",
-				"-expect-alpn", "baz",
-			},
-		})
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "ALPNServer-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				NextProtos: []string{"foo", "bar", "baz"},
-			},
-			flags: []string{
-				"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
-				"-select-alpn", "foo",
-			},
-			expectations: connectionExpectations{
-				nextProto:     "foo",
-				nextProtoType: alpn,
-			},
-			resumeSession: true,
-		})
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "ALPNServer-Decline-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				NextProtos: []string{"foo", "bar", "baz"},
-			},
-			flags: []string{"-decline-alpn"},
-			expectations: connectionExpectations{
-				noNextProto: true,
-			},
-			resumeSession: true,
-		})
-		// Test that the server implementation catches itself if the
-		// callback tries to return an invalid empty ALPN protocol.
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "ALPNServer-SelectEmpty-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				NextProtos: []string{"foo", "bar", "baz"},
-			},
-			flags: []string{
-				"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
-				"-select-empty-alpn",
-			},
-			shouldFail:         true,
-			expectedLocalError: "remote error: internal error",
-			expectedError:      ":INVALID_ALPN_PROTOCOL:",
-		})
-
-		// Test ALPN in async mode as well to ensure that extensions callbacks are only
-		// called once.
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "ALPNServer-Async-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				NextProtos: []string{"foo", "bar", "baz"},
-				// Prior to TLS 1.3, exercise the asynchronous session callback.
-				SessionTicketsDisabled: ver.version < VersionTLS13,
-			},
-			flags: []string{
-				"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
-				"-select-alpn", "foo",
-				"-async",
-			},
-			expectations: connectionExpectations{
-				nextProto:     "foo",
-				nextProtoType: alpn,
-			},
-			resumeSession: true,
-		})
-
-		var emptyString string
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "ALPNClient-EmptyProtocolName-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				NextProtos: []string{""},
-				Bugs: ProtocolBugs{
-					// A server returning an empty ALPN protocol
-					// should be rejected.
-					ALPNProtocol: &emptyString,
-				},
-			},
-			flags: []string{
-				"-advertise-alpn", "\x03foo",
-			},
-			shouldFail:    true,
-			expectedError: ":PARSE_TLSEXT:",
-		})
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "ALPNServer-EmptyProtocolName-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				// A ClientHello containing an empty ALPN protocol
-				// should be rejected.
-				NextProtos: []string{"foo", "", "baz"},
-			},
-			flags: []string{
-				"-select-alpn", "foo",
-			},
-			shouldFail:    true,
-			expectedError: ":PARSE_TLSEXT:",
-		})
-
-		// Test NPN and the interaction with ALPN.
-		if ver.version < VersionTLS13 {
-			// Test that the server prefers ALPN over NPN.
+			// Test that duplicate extensions are rejected.
 			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "ALPNServer-Preferred-" + ver.name,
-				config: Config{
-					MaxVersion: ver.version,
-					NextProtos: []string{"foo", "bar", "baz"},
-				},
-				flags: []string{
-					"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
-					"-select-alpn", "foo",
-					"-advertise-npn", "\x03foo\x03bar\x03baz",
-				},
-				expectations: connectionExpectations{
-					nextProto:     "foo",
-					nextProtoType: alpn,
-				},
-				resumeSession: true,
-			})
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "ALPNServer-Preferred-Swapped-" + ver.name,
-				config: Config{
-					MaxVersion: ver.version,
-					NextProtos: []string{"foo", "bar", "baz"},
-					Bugs: ProtocolBugs{
-						SwapNPNAndALPN: true,
-					},
-				},
-				flags: []string{
-					"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
-					"-select-alpn", "foo",
-					"-advertise-npn", "\x03foo\x03bar\x03baz",
-				},
-				expectations: connectionExpectations{
-					nextProto:     "foo",
-					nextProtoType: alpn,
-				},
-				resumeSession: true,
-			})
-
-			// Test that negotiating both NPN and ALPN is forbidden.
-			testCases = append(testCases, testCase{
-				name: "NegotiateALPNAndNPN-" + ver.name,
-				config: Config{
-					MaxVersion: ver.version,
-					NextProtos: []string{"foo", "bar", "baz"},
-					Bugs: ProtocolBugs{
-						NegotiateALPNAndNPN: true,
-					},
-				},
-				flags: []string{
-					"-advertise-alpn", "\x03foo",
-					"-select-next-proto", "foo",
-				},
-				shouldFail:    true,
-				expectedError: ":NEGOTIATED_BOTH_NPN_AND_ALPN:",
-			})
-			testCases = append(testCases, testCase{
-				name: "NegotiateALPNAndNPN-Swapped-" + ver.name,
-				config: Config{
-					MaxVersion: ver.version,
-					NextProtos: []string{"foo", "bar", "baz"},
-					Bugs: ProtocolBugs{
-						NegotiateALPNAndNPN: true,
-						SwapNPNAndALPN:      true,
-					},
-				},
-				flags: []string{
-					"-advertise-alpn", "\x03foo",
-					"-select-next-proto", "foo",
-				},
-				shouldFail:    true,
-				expectedError: ":NEGOTIATED_BOTH_NPN_AND_ALPN:",
-			})
-		}
-
-		// Test missing ALPN in QUIC
-		if ver.version >= VersionTLS13 {
-			testCases = append(testCases, testCase{
+				protocol: protocol,
 				testType: clientTest,
-				protocol: quic,
-				name:     "QUIC-Client-ALPNMissingFromConfig-" + ver.name,
+				name:     "DuplicateExtensionClient-" + suffix,
 				config: Config{
-					MinVersion: ver.version,
 					MaxVersion: ver.version,
+					Bugs: ProtocolBugs{
+						DuplicateExtension: true,
+					},
 				},
-				skipQUICALPNConfig: true,
 				shouldFail:         true,
-				expectedError:      ":MISSING_ALPN:",
+				expectedLocalError: "remote error: error decoding message",
 			})
 			testCases = append(testCases, testCase{
+				protocol: protocol,
+				testType: serverTest,
+				name:     "DuplicateExtensionServer-" + suffix,
+				config: Config{
+					MaxVersion: ver.version,
+					Bugs: ProtocolBugs{
+						DuplicateExtension: true,
+					},
+				},
+				shouldFail:         true,
+				expectedLocalError: "remote error: error decoding message",
+			})
+
+			// Test SNI.
+			testCases = append(testCases, testCase{
+				protocol: protocol,
 				testType: clientTest,
-				protocol: quic,
-				name:     "QUIC-Client-ALPNMissing-" + ver.name,
+				name:     "ServerNameExtensionClient-" + suffix,
 				config: Config{
-					MinVersion: ver.version,
 					MaxVersion: ver.version,
+					Bugs: ProtocolBugs{
+						ExpectServerName: "example.com",
+					},
 				},
-				flags: []string{
-					"-advertise-alpn", "\x03foo",
-				},
-				skipQUICALPNConfig: true,
-				shouldFail:         true,
-				expectedError:      ":MISSING_ALPN:",
-				expectedLocalError: "remote error: no application protocol",
+				flags: []string{"-host-name", "example.com"},
 			})
 			testCases = append(testCases, testCase{
-				testType: serverTest,
-				protocol: quic,
-				name:     "QUIC-Server-ALPNMissing-" + ver.name,
-				config: Config{
-					MinVersion: ver.version,
-					MaxVersion: ver.version,
-				},
-				skipQUICALPNConfig: true,
-				shouldFail:         true,
-				expectedError:      ":MISSING_ALPN:",
-				expectedLocalError: "remote error: no application protocol",
-			})
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				protocol: quic,
-				name:     "QUIC-Server-ALPNMismatch-" + ver.name,
-				config: Config{
-					MinVersion: ver.version,
-					MaxVersion: ver.version,
-					NextProtos: []string{"foo"},
-				},
-				flags: []string{
-					"-decline-alpn",
-				},
-				skipQUICALPNConfig: true,
-				shouldFail:         true,
-				expectedError:      ":MISSING_ALPN:",
-				expectedLocalError: "remote error: no application protocol",
-			})
-		}
-
-		// Test ALPS.
-		if ver.version >= VersionTLS13 {
-			// Test that client and server can negotiate ALPS, including
-			// different values on resumption.
-			testCases = append(testCases, testCase{
+				protocol: protocol,
 				testType: clientTest,
-				name:     "ALPS-Basic-Client-" + ver.name,
-				config: Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"proto"},
-					ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
-				},
-				resumeConfig: &Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"proto"},
-					ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
-				},
-				resumeSession: true,
-				expectations: connectionExpectations{
-					peerApplicationSettings: []byte("shim1"),
-				},
-				resumeExpectations: &connectionExpectations{
-					peerApplicationSettings: []byte("shim2"),
-				},
-				flags: []string{
-					"-advertise-alpn", "\x05proto",
-					"-expect-alpn", "proto",
-					"-on-initial-application-settings", "proto,shim1",
-					"-on-initial-expect-peer-application-settings", "runner1",
-					"-on-resume-application-settings", "proto,shim2",
-					"-on-resume-expect-peer-application-settings", "runner2",
-				},
-			})
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "ALPS-Basic-Server-" + ver.name,
-				config: Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"proto"},
-					ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
-				},
-				resumeConfig: &Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"proto"},
-					ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
-				},
-				resumeSession: true,
-				expectations: connectionExpectations{
-					peerApplicationSettings: []byte("shim1"),
-				},
-				resumeExpectations: &connectionExpectations{
-					peerApplicationSettings: []byte("shim2"),
-				},
-				flags: []string{
-					"-select-alpn", "proto",
-					"-on-initial-application-settings", "proto,shim1",
-					"-on-initial-expect-peer-application-settings", "runner1",
-					"-on-resume-application-settings", "proto,shim2",
-					"-on-resume-expect-peer-application-settings", "runner2",
-				},
-			})
-
-			// TODO(davidben): This is a copy of ALPS-Basic-Server to test a
-			// QUIC-specific ALPS issue. Remove this and instead run all the
-			// tests in this function at all protocols.
-			testCases = append(testCases, testCase{
-				protocol: quic,
-				testType: serverTest,
-				name:     "ALPS-Basic-Server-QUIC-" + ver.name,
-				skipQUICALPNConfig: true,
-				config: Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"proto"},
-					ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
-				},
-				resumeConfig: &Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"proto"},
-					ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
-				},
-				resumeSession: true,
-				expectations: connectionExpectations{
-					peerApplicationSettings: []byte("shim1"),
-				},
-				resumeExpectations: &connectionExpectations{
-					peerApplicationSettings: []byte("shim2"),
-				},
-				flags: []string{
-					"-select-alpn", "proto",
-					"-on-initial-application-settings", "proto,shim1",
-					"-on-initial-expect-peer-application-settings", "runner1",
-					"-on-resume-application-settings", "proto,shim2",
-					"-on-resume-expect-peer-application-settings", "runner2",
-				},
-			})
-
-			// Test that the server can defer its ALPS configuration to the ALPN
-			// selection callback.
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "ALPS-Basic-Server-Defer-" + ver.name,
-				config: Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"proto"},
-					ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
-				},
-				resumeConfig: &Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"proto"},
-					ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
-				},
-				resumeSession: true,
-				expectations: connectionExpectations{
-					peerApplicationSettings: []byte("shim1"),
-				},
-				resumeExpectations: &connectionExpectations{
-					peerApplicationSettings: []byte("shim2"),
-				},
-				flags: []string{
-					"-select-alpn", "proto",
-					"-defer-alps",
-					"-on-initial-application-settings", "proto,shim1",
-					"-on-initial-expect-peer-application-settings", "runner1",
-					"-on-resume-application-settings", "proto,shim2",
-					"-on-resume-expect-peer-application-settings", "runner2",
-				},
-			})
-
-			// Test the client and server correctly handle empty settings.
-			testCases = append(testCases, testCase{
-				testType: clientTest,
-				name:     "ALPS-Empty-Client-" + ver.name,
-				config: Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"proto"},
-					ApplicationSettings: map[string][]byte{"proto": []byte{}},
-				},
-				resumeSession: true,
-				expectations: connectionExpectations{
-					peerApplicationSettings: []byte{},
-				},
-				flags: []string{
-					"-advertise-alpn", "\x05proto",
-					"-expect-alpn", "proto",
-					"-application-settings", "proto,",
-					"-expect-peer-application-settings", "",
-				},
-			})
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "ALPS-Empty-Server-" + ver.name,
-				config: Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"proto"},
-					ApplicationSettings: map[string][]byte{"proto": []byte{}},
-				},
-				resumeSession: true,
-				expectations: connectionExpectations{
-					peerApplicationSettings: []byte{},
-				},
-				flags: []string{
-					"-select-alpn", "proto",
-					"-application-settings", "proto,",
-					"-expect-peer-application-settings", "",
-				},
-			})
-
-			// Test the client rejects application settings from the server on
-			// protocols it doesn't have them.
-			testCases = append(testCases, testCase{
-				testType: clientTest,
-				name:     "ALPS-UnsupportedProtocol-Client-" + ver.name,
-				config: Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"proto1"},
-					ApplicationSettings: map[string][]byte{"proto1": []byte("runner")},
-					Bugs: ProtocolBugs{
-						AlwaysNegotiateApplicationSettings: true,
-					},
-				},
-				// The client supports ALPS with "proto2", but not "proto1".
-				flags: []string{
-					"-advertise-alpn", "\x06proto1\x06proto2",
-					"-application-settings", "proto2,shim",
-					"-expect-alpn", "proto1",
-				},
-				// The server sends ALPS with "proto1", which is invalid.
-				shouldFail:         true,
-				expectedError:      ":INVALID_ALPN_PROTOCOL:",
-				expectedLocalError: "remote error: illegal parameter",
-			})
-
-			// Test the server declines ALPS if it doesn't support it for the
-			// specified protocol.
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "ALPS-UnsupportedProtocol-Server-" + ver.name,
-				config: Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"proto1"},
-					ApplicationSettings: map[string][]byte{"proto1": []byte("runner")},
-				},
-				// The server supports ALPS with "proto2", but not "proto1".
-				flags: []string{
-					"-select-alpn", "proto1",
-					"-application-settings", "proto2,shim",
-				},
-			})
-
-			// Test that the server rejects a missing application_settings extension.
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "ALPS-OmitClientApplicationSettings-" + ver.name,
-				config: Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"proto"},
-					ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
-					Bugs: ProtocolBugs{
-						OmitClientApplicationSettings: true,
-					},
-				},
-				flags: []string{
-					"-select-alpn", "proto",
-					"-application-settings", "proto,shim",
-				},
-				// The runner is a client, so it only processes the shim's alert
-				// after checking connection state.
-				expectations: connectionExpectations{
-					peerApplicationSettings: []byte("shim"),
-				},
-				shouldFail:         true,
-				expectedError:      ":MISSING_EXTENSION:",
-				expectedLocalError: "remote error: missing extension",
-			})
-
-			// Test that the server rejects a missing EncryptedExtensions message.
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "ALPS-OmitClientEncryptedExtensions-" + ver.name,
-				config: Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"proto"},
-					ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
-					Bugs: ProtocolBugs{
-						OmitClientEncryptedExtensions: true,
-					},
-				},
-				flags: []string{
-					"-select-alpn", "proto",
-					"-application-settings", "proto,shim",
-				},
-				// The runner is a client, so it only processes the shim's alert
-				// after checking connection state.
-				expectations: connectionExpectations{
-					peerApplicationSettings: []byte("shim"),
-				},
-				shouldFail:         true,
-				expectedError:      ":UNEXPECTED_MESSAGE:",
-				expectedLocalError: "remote error: unexpected message",
-			})
-
-			// Test that the server rejects an unexpected EncryptedExtensions message.
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "UnexpectedClientEncryptedExtensions-" + ver.name,
+				name:     "ServerNameExtensionClientMismatch-" + suffix,
 				config: Config{
 					MaxVersion: ver.version,
 					Bugs: ProtocolBugs{
-						AlwaysSendClientEncryptedExtensions: true,
+						ExpectServerName: "mismatch.com",
+					},
+				},
+				flags:              []string{"-host-name", "example.com"},
+				shouldFail:         true,
+				expectedLocalError: "tls: unexpected server name",
+			})
+			testCases = append(testCases, testCase{
+				protocol: protocol,
+				testType: clientTest,
+				name:     "ServerNameExtensionClientMissing-" + suffix,
+				config: Config{
+					MaxVersion: ver.version,
+					Bugs: ProtocolBugs{
+						ExpectServerName: "missing.com",
 					},
 				},
 				shouldFail:         true,
-				expectedError:      ":UNEXPECTED_MESSAGE:",
-				expectedLocalError: "remote error: unexpected message",
+				expectedLocalError: "tls: unexpected server name",
 			})
-
-			// Test that the server rejects an unexpected extension in an
-			// expected EncryptedExtensions message.
 			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "ExtraClientEncryptedExtension-" + ver.name,
+				protocol: protocol,
+				testType: clientTest,
+				name:     "TolerateServerNameAck-" + suffix,
 				config: Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"proto"},
-					ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
+					MaxVersion: ver.version,
 					Bugs: ProtocolBugs{
-						SendExtraClientEncryptedExtension: true,
+						SendServerNameAck: true,
 					},
 				},
-				flags: []string{
-					"-select-alpn", "proto",
-					"-application-settings", "proto,shim",
-				},
-				// The runner is a client, so it only processes the shim's alert
-				// after checking connection state.
-				expectations: connectionExpectations{
-					peerApplicationSettings: []byte("shim"),
+				flags:         []string{"-host-name", "example.com"},
+				resumeSession: true,
+			})
+			testCases = append(testCases, testCase{
+				protocol: protocol,
+				testType: clientTest,
+				name:     "UnsolicitedServerNameAck-" + suffix,
+				config: Config{
+					MaxVersion: ver.version,
+					Bugs: ProtocolBugs{
+						SendServerNameAck: true,
+					},
 				},
 				shouldFail:         true,
 				expectedError:      ":UNEXPECTED_EXTENSION:",
 				expectedLocalError: "remote error: unsupported extension",
 			})
+			testCases = append(testCases, testCase{
+				protocol: protocol,
+				testType: serverTest,
+				name:     "ServerNameExtensionServer-" + suffix,
+				config: Config{
+					MaxVersion: ver.version,
+					ServerName: "example.com",
+				},
+				flags:         []string{"-expect-server-name", "example.com"},
+				resumeSession: true,
+			})
 
-			// Test that ALPS is carried over on 0-RTT.
-			for _, empty := range []bool{false, true} {
-				suffix := ver.name
-				runnerSettings := "runner"
-				shimSettings := "shim"
-				if empty {
-					suffix = "Empty-" + ver.name
-					runnerSettings = ""
-					shimSettings = ""
-				}
-
-				testCases = append(testCases, testCase{
-					testType: clientTest,
-					name:     "ALPS-EarlyData-Client-" + suffix,
-					config: Config{
-						MaxVersion:          ver.version,
-						NextProtos:          []string{"proto"},
-						ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
+			// Test ALPN.
+			testCases = append(testCases, testCase{
+				protocol:           protocol,
+				testType:           clientTest,
+				skipQUICALPNConfig: true,
+				name:               "ALPNClient-" + suffix,
+				config: Config{
+					MaxVersion: ver.version,
+					NextProtos: []string{"foo"},
+				},
+				flags: []string{
+					"-advertise-alpn", "\x03foo\x03bar\x03baz",
+					"-expect-alpn", "foo",
+				},
+				expectations: connectionExpectations{
+					nextProto:     "foo",
+					nextProtoType: alpn,
+				},
+				resumeSession: true,
+			})
+			testCases = append(testCases, testCase{
+				protocol:           protocol,
+				testType:           clientTest,
+				skipQUICALPNConfig: true,
+				name:               "ALPNClient-RejectUnknown-" + suffix,
+				config: Config{
+					MaxVersion: ver.version,
+					Bugs: ProtocolBugs{
+						SendALPN: "baz",
 					},
-					resumeSession: true,
-					earlyData:     true,
+				},
+				flags: []string{
+					"-advertise-alpn", "\x03foo\x03bar",
+				},
+				shouldFail:         true,
+				expectedError:      ":INVALID_ALPN_PROTOCOL:",
+				expectedLocalError: "remote error: illegal parameter",
+			})
+			testCases = append(testCases, testCase{
+				protocol:           protocol,
+				testType:           clientTest,
+				skipQUICALPNConfig: true,
+				name:               "ALPNClient-AllowUnknown-" + suffix,
+				config: Config{
+					MaxVersion: ver.version,
+					Bugs: ProtocolBugs{
+						SendALPN: "baz",
+					},
+				},
+				flags: []string{
+					"-advertise-alpn", "\x03foo\x03bar",
+					"-allow-unknown-alpn-protos",
+					"-expect-alpn", "baz",
+				},
+			})
+			testCases = append(testCases, testCase{
+				protocol:           protocol,
+				testType:           serverTest,
+				skipQUICALPNConfig: true,
+				name:               "ALPNServer-" + suffix,
+				config: Config{
+					MaxVersion: ver.version,
+					NextProtos: []string{"foo", "bar", "baz"},
+				},
+				flags: []string{
+					"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
+					"-select-alpn", "foo",
+				},
+				expectations: connectionExpectations{
+					nextProto:     "foo",
+					nextProtoType: alpn,
+				},
+				resumeSession: true,
+			})
+
+			var shouldDeclineALPNFail bool
+			var declineALPNError, declineALPNLocalError string
+			if protocol == quic {
+				// ALPN is mandatory in QUIC.
+				shouldDeclineALPNFail = true
+				declineALPNError = ":MISSING_ALPN:"
+				declineALPNLocalError = "remote error: no application protocol"
+			}
+			testCases = append(testCases, testCase{
+				protocol:           protocol,
+				testType:           serverTest,
+				skipQUICALPNConfig: true,
+				name:               "ALPNServer-Decline-" + suffix,
+				config: Config{
+					MaxVersion: ver.version,
+					NextProtos: []string{"foo", "bar", "baz"},
+				},
+				flags: []string{"-decline-alpn"},
+				expectations: connectionExpectations{
+					noNextProto: true,
+				},
+				resumeSession:      true,
+				shouldFail:         shouldDeclineALPNFail,
+				expectedError:      declineALPNError,
+				expectedLocalError: declineALPNLocalError,
+			})
+
+			// Test that the server implementation catches itself if the
+			// callback tries to return an invalid empty ALPN protocol.
+			testCases = append(testCases, testCase{
+				protocol:           protocol,
+				testType:           serverTest,
+				skipQUICALPNConfig: true,
+				name:               "ALPNServer-SelectEmpty-" + suffix,
+				config: Config{
+					MaxVersion: ver.version,
+					NextProtos: []string{"foo", "bar", "baz"},
+				},
+				flags: []string{
+					"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
+					"-select-empty-alpn",
+				},
+				shouldFail:         true,
+				expectedLocalError: "remote error: internal error",
+				expectedError:      ":INVALID_ALPN_PROTOCOL:",
+			})
+
+			// Test ALPN in async mode as well to ensure that extensions callbacks are only
+			// called once.
+			testCases = append(testCases, testCase{
+				protocol:           protocol,
+				testType:           serverTest,
+				skipQUICALPNConfig: true,
+				name:               "ALPNServer-Async-" + suffix,
+				config: Config{
+					MaxVersion: ver.version,
+					NextProtos: []string{"foo", "bar", "baz"},
+					// Prior to TLS 1.3, exercise the asynchronous session callback.
+					SessionTicketsDisabled: ver.version < VersionTLS13,
+				},
+				flags: []string{
+					"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
+					"-select-alpn", "foo",
+					"-async",
+				},
+				expectations: connectionExpectations{
+					nextProto:     "foo",
+					nextProtoType: alpn,
+				},
+				resumeSession: true,
+			})
+
+			var emptyString string
+			testCases = append(testCases, testCase{
+				protocol:           protocol,
+				testType:           clientTest,
+				skipQUICALPNConfig: true,
+				name:               "ALPNClient-EmptyProtocolName-" + suffix,
+				config: Config{
+					MaxVersion: ver.version,
+					NextProtos: []string{""},
+					Bugs: ProtocolBugs{
+						// A server returning an empty ALPN protocol
+						// should be rejected.
+						ALPNProtocol: &emptyString,
+					},
+				},
+				flags: []string{
+					"-advertise-alpn", "\x03foo",
+				},
+				shouldFail:    true,
+				expectedError: ":PARSE_TLSEXT:",
+			})
+			testCases = append(testCases, testCase{
+				protocol:           protocol,
+				testType:           serverTest,
+				skipQUICALPNConfig: true,
+				name:               "ALPNServer-EmptyProtocolName-" + suffix,
+				config: Config{
+					MaxVersion: ver.version,
+					// A ClientHello containing an empty ALPN protocol
+					// should be rejected.
+					NextProtos: []string{"foo", "", "baz"},
+				},
+				flags: []string{
+					"-select-alpn", "foo",
+				},
+				shouldFail:    true,
+				expectedError: ":PARSE_TLSEXT:",
+			})
+
+			// Test NPN and the interaction with ALPN.
+			if ver.version < VersionTLS13 && protocol == tls {
+				// Test that the server prefers ALPN over NPN.
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: serverTest,
+					name:     "ALPNServer-Preferred-" + suffix,
+					config: Config{
+						MaxVersion: ver.version,
+						NextProtos: []string{"foo", "bar", "baz"},
+					},
 					flags: []string{
-						"-advertise-alpn", "\x05proto",
-						"-expect-alpn", "proto",
-						"-application-settings", "proto," + shimSettings,
-						"-expect-peer-application-settings", runnerSettings,
+						"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
+						"-select-alpn", "foo",
+						"-advertise-npn", "\x03foo\x03bar\x03baz",
 					},
 					expectations: connectionExpectations{
-						peerApplicationSettings: []byte(shimSettings),
+						nextProto:     "foo",
+						nextProtoType: alpn,
 					},
+					resumeSession: true,
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: serverTest,
+					name:     "ALPNServer-Preferred-Swapped-" + suffix,
+					config: Config{
+						MaxVersion: ver.version,
+						NextProtos: []string{"foo", "bar", "baz"},
+						Bugs: ProtocolBugs{
+							SwapNPNAndALPN: true,
+						},
+					},
+					flags: []string{
+						"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
+						"-select-alpn", "foo",
+						"-advertise-npn", "\x03foo\x03bar\x03baz",
+					},
+					expectations: connectionExpectations{
+						nextProto:     "foo",
+						nextProtoType: alpn,
+					},
+					resumeSession: true,
+				})
+
+				// Test that negotiating both NPN and ALPN is forbidden.
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					name:     "NegotiateALPNAndNPN-" + suffix,
+					config: Config{
+						MaxVersion: ver.version,
+						NextProtos: []string{"foo", "bar", "baz"},
+						Bugs: ProtocolBugs{
+							NegotiateALPNAndNPN: true,
+						},
+					},
+					flags: []string{
+						"-advertise-alpn", "\x03foo",
+						"-select-next-proto", "foo",
+					},
+					shouldFail:    true,
+					expectedError: ":NEGOTIATED_BOTH_NPN_AND_ALPN:",
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					name:     "NegotiateALPNAndNPN-Swapped-" + suffix,
+					config: Config{
+						MaxVersion: ver.version,
+						NextProtos: []string{"foo", "bar", "baz"},
+						Bugs: ProtocolBugs{
+							NegotiateALPNAndNPN: true,
+							SwapNPNAndALPN:      true,
+						},
+					},
+					flags: []string{
+						"-advertise-alpn", "\x03foo",
+						"-select-next-proto", "foo",
+					},
+					shouldFail:    true,
+					expectedError: ":NEGOTIATED_BOTH_NPN_AND_ALPN:",
+				})
+			}
+
+			// Test missing ALPN in QUIC
+			if protocol == quic {
+				testCases = append(testCases, testCase{
+					testType: clientTest,
+					protocol: protocol,
+					name:     "Client-ALPNMissingFromConfig-" + suffix,
+					config: Config{
+						MinVersion: ver.version,
+						MaxVersion: ver.version,
+					},
+					skipQUICALPNConfig: true,
+					shouldFail:         true,
+					expectedError:      ":MISSING_ALPN:",
+				})
+				testCases = append(testCases, testCase{
+					testType: clientTest,
+					protocol: protocol,
+					name:     "Client-ALPNMissing-" + suffix,
+					config: Config{
+						MinVersion: ver.version,
+						MaxVersion: ver.version,
+					},
+					flags: []string{
+						"-advertise-alpn", "\x03foo",
+					},
+					skipQUICALPNConfig: true,
+					shouldFail:         true,
+					expectedError:      ":MISSING_ALPN:",
+					expectedLocalError: "remote error: no application protocol",
 				})
 				testCases = append(testCases, testCase{
 					testType: serverTest,
-					name:     "ALPS-EarlyData-Server-" + suffix,
+					protocol: protocol,
+					name:     "Server-ALPNMissing-" + suffix,
 					config: Config{
-						MaxVersion:          ver.version,
-						NextProtos:          []string{"proto"},
-						ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
+						MinVersion: ver.version,
+						MaxVersion: ver.version,
 					},
-					resumeSession: true,
-					earlyData:     true,
-					flags: []string{
-						"-select-alpn", "proto",
-						"-application-settings", "proto," + shimSettings,
-						"-expect-peer-application-settings", runnerSettings,
-					},
-					expectations: connectionExpectations{
-						peerApplicationSettings: []byte(shimSettings),
-					},
+					skipQUICALPNConfig: true,
+					shouldFail:         true,
+					expectedError:      ":MISSING_ALPN:",
+					expectedLocalError: "remote error: no application protocol",
 				})
-
-				// Sending application settings in 0-RTT handshakes is forbidden.
 				testCases = append(testCases, testCase{
-					testType: clientTest,
-					name:     "ALPS-EarlyData-SendApplicationSettingsWithEarlyData-Client-" + suffix,
+					testType: serverTest,
+					protocol: protocol,
+					name:     "Server-ALPNMismatch-" + suffix,
+					config: Config{
+						MinVersion: ver.version,
+						MaxVersion: ver.version,
+						NextProtos: []string{"foo"},
+					},
+					flags: []string{
+						"-decline-alpn",
+					},
+					skipQUICALPNConfig: true,
+					shouldFail:         true,
+					expectedError:      ":MISSING_ALPN:",
+					expectedLocalError: "remote error: no application protocol",
+				})
+			}
+
+			// Test ALPS.
+			if ver.version >= VersionTLS13 {
+				// Test that client and server can negotiate ALPS, including
+				// different values on resumption.
+				testCases = append(testCases, testCase{
+					protocol:           protocol,
+					testType:           clientTest,
+					name:               "ALPS-Basic-Client-" + suffix,
+					skipQUICALPNConfig: true,
 					config: Config{
 						MaxVersion:          ver.version,
 						NextProtos:          []string{"proto"},
-						ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
-						Bugs: ProtocolBugs{
-							SendApplicationSettingsWithEarlyData: true,
-						},
+						ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
+					},
+					resumeConfig: &Config{
+						MaxVersion:          ver.version,
+						NextProtos:          []string{"proto"},
+						ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
 					},
 					resumeSession: true,
-					earlyData:     true,
+					expectations: connectionExpectations{
+						peerApplicationSettings: []byte("shim1"),
+					},
+					resumeExpectations: &connectionExpectations{
+						peerApplicationSettings: []byte("shim2"),
+					},
 					flags: []string{
 						"-advertise-alpn", "\x05proto",
 						"-expect-alpn", "proto",
-						"-application-settings", "proto," + shimSettings,
-						"-expect-peer-application-settings", runnerSettings,
+						"-on-initial-application-settings", "proto,shim1",
+						"-on-initial-expect-peer-application-settings", "runner1",
+						"-on-resume-application-settings", "proto,shim2",
+						"-on-resume-expect-peer-application-settings", "runner2",
 					},
+				})
+				testCases = append(testCases, testCase{
+					protocol:           protocol,
+					testType:           serverTest,
+					name:               "ALPS-Basic-Server-" + suffix,
+					skipQUICALPNConfig: true,
+					config: Config{
+						MaxVersion:          ver.version,
+						NextProtos:          []string{"proto"},
+						ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
+					},
+					resumeConfig: &Config{
+						MaxVersion:          ver.version,
+						NextProtos:          []string{"proto"},
+						ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
+					},
+					resumeSession: true,
 					expectations: connectionExpectations{
-						peerApplicationSettings: []byte(shimSettings),
+						peerApplicationSettings: []byte("shim1"),
 					},
+					resumeExpectations: &connectionExpectations{
+						peerApplicationSettings: []byte("shim2"),
+					},
+					flags: []string{
+						"-select-alpn", "proto",
+						"-on-initial-application-settings", "proto,shim1",
+						"-on-initial-expect-peer-application-settings", "runner1",
+						"-on-resume-application-settings", "proto,shim2",
+						"-on-resume-expect-peer-application-settings", "runner2",
+					},
+				})
+
+				// Test that the server can defer its ALPS configuration to the ALPN
+				// selection callback.
+				testCases = append(testCases, testCase{
+					protocol:           protocol,
+					testType:           serverTest,
+					name:               "ALPS-Basic-Server-Defer-" + suffix,
+					skipQUICALPNConfig: true,
+					config: Config{
+						MaxVersion:          ver.version,
+						NextProtos:          []string{"proto"},
+						ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
+					},
+					resumeConfig: &Config{
+						MaxVersion:          ver.version,
+						NextProtos:          []string{"proto"},
+						ApplicationSettings: map[string][]byte{"proto": []byte("runner2")},
+					},
+					resumeSession: true,
+					expectations: connectionExpectations{
+						peerApplicationSettings: []byte("shim1"),
+					},
+					resumeExpectations: &connectionExpectations{
+						peerApplicationSettings: []byte("shim2"),
+					},
+					flags: []string{
+						"-select-alpn", "proto",
+						"-defer-alps",
+						"-on-initial-application-settings", "proto,shim1",
+						"-on-initial-expect-peer-application-settings", "runner1",
+						"-on-resume-application-settings", "proto,shim2",
+						"-on-resume-expect-peer-application-settings", "runner2",
+					},
+				})
+
+				// Test the client and server correctly handle empty settings.
+				testCases = append(testCases, testCase{
+					protocol:           protocol,
+					testType:           clientTest,
+					name:               "ALPS-Empty-Client-" + suffix,
+					skipQUICALPNConfig: true,
+					config: Config{
+						MaxVersion:          ver.version,
+						NextProtos:          []string{"proto"},
+						ApplicationSettings: map[string][]byte{"proto": []byte{}},
+					},
+					resumeSession: true,
+					expectations: connectionExpectations{
+						peerApplicationSettings: []byte{},
+					},
+					flags: []string{
+						"-advertise-alpn", "\x05proto",
+						"-expect-alpn", "proto",
+						"-application-settings", "proto,",
+						"-expect-peer-application-settings", "",
+					},
+				})
+				testCases = append(testCases, testCase{
+					protocol:           protocol,
+					testType:           serverTest,
+					name:               "ALPS-Empty-Server-" + suffix,
+					skipQUICALPNConfig: true,
+					config: Config{
+						MaxVersion:          ver.version,
+						NextProtos:          []string{"proto"},
+						ApplicationSettings: map[string][]byte{"proto": []byte{}},
+					},
+					resumeSession: true,
+					expectations: connectionExpectations{
+						peerApplicationSettings: []byte{},
+					},
+					flags: []string{
+						"-select-alpn", "proto",
+						"-application-settings", "proto,",
+						"-expect-peer-application-settings", "",
+					},
+				})
+
+				// Test the client rejects application settings from the server on
+				// protocols it doesn't have them.
+				testCases = append(testCases, testCase{
+					protocol:           protocol,
+					testType:           clientTest,
+					name:               "ALPS-UnsupportedProtocol-Client-" + suffix,
+					skipQUICALPNConfig: true,
+					config: Config{
+						MaxVersion:          ver.version,
+						NextProtos:          []string{"proto1"},
+						ApplicationSettings: map[string][]byte{"proto1": []byte("runner")},
+						Bugs: ProtocolBugs{
+							AlwaysNegotiateApplicationSettings: true,
+						},
+					},
+					// The client supports ALPS with "proto2", but not "proto1".
+					flags: []string{
+						"-advertise-alpn", "\x06proto1\x06proto2",
+						"-application-settings", "proto2,shim",
+						"-expect-alpn", "proto1",
+					},
+					// The server sends ALPS with "proto1", which is invalid.
 					shouldFail:         true,
-					expectedError:      ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:",
+					expectedError:      ":INVALID_ALPN_PROTOCOL:",
 					expectedLocalError: "remote error: illegal parameter",
 				})
+
+				// Test the server declines ALPS if it doesn't support it for the
+				// specified protocol.
 				testCases = append(testCases, testCase{
-					testType: serverTest,
-					name:     "ALPS-EarlyData-SendApplicationSettingsWithEarlyData-Server-" + suffix,
+					protocol:           protocol,
+					testType:           serverTest,
+					name:               "ALPS-UnsupportedProtocol-Server-" + suffix,
+					skipQUICALPNConfig: true,
+					config: Config{
+						MaxVersion:          ver.version,
+						NextProtos:          []string{"proto1"},
+						ApplicationSettings: map[string][]byte{"proto1": []byte("runner")},
+					},
+					// The server supports ALPS with "proto2", but not "proto1".
+					flags: []string{
+						"-select-alpn", "proto1",
+						"-application-settings", "proto2,shim",
+					},
+				})
+
+				// Test that the server rejects a missing application_settings extension.
+				testCases = append(testCases, testCase{
+					protocol:           protocol,
+					testType:           serverTest,
+					name:               "ALPS-OmitClientApplicationSettings-" + suffix,
+					skipQUICALPNConfig: true,
 					config: Config{
 						MaxVersion:          ver.version,
 						NextProtos:          []string{"proto"},
-						ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
+						ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
 						Bugs: ProtocolBugs{
-							SendApplicationSettingsWithEarlyData: true,
+							OmitClientApplicationSettings: true,
 						},
 					},
-					resumeSession: true,
-					earlyData:     true,
 					flags: []string{
 						"-select-alpn", "proto",
-						"-application-settings", "proto," + shimSettings,
-						"-expect-peer-application-settings", runnerSettings,
+						"-application-settings", "proto,shim",
 					},
+					// The runner is a client, so it only processes the shim's alert
+					// after checking connection state.
 					expectations: connectionExpectations{
-						peerApplicationSettings: []byte(shimSettings),
+						peerApplicationSettings: []byte("shim"),
+					},
+					shouldFail:         true,
+					expectedError:      ":MISSING_EXTENSION:",
+					expectedLocalError: "remote error: missing extension",
+				})
+
+				// Test that the server rejects a missing EncryptedExtensions message.
+				testCases = append(testCases, testCase{
+					protocol:           protocol,
+					testType:           serverTest,
+					name:               "ALPS-OmitClientEncryptedExtensions-" + suffix,
+					skipQUICALPNConfig: true,
+					config: Config{
+						MaxVersion:          ver.version,
+						NextProtos:          []string{"proto"},
+						ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
+						Bugs: ProtocolBugs{
+							OmitClientEncryptedExtensions: true,
+						},
+					},
+					flags: []string{
+						"-select-alpn", "proto",
+						"-application-settings", "proto,shim",
+					},
+					// The runner is a client, so it only processes the shim's alert
+					// after checking connection state.
+					expectations: connectionExpectations{
+						peerApplicationSettings: []byte("shim"),
 					},
 					shouldFail:         true,
 					expectedError:      ":UNEXPECTED_MESSAGE:",
 					expectedLocalError: "remote error: unexpected message",
 				})
-			}
 
-			// Test that the client and server each decline early data if local
-			// ALPS preferences has changed for the current connection.
-			alpsMismatchTests := []struct {
-				name                            string
-				initialSettings, resumeSettings []byte
-			}{
-				{"DifferentValues", []byte("settings1"), []byte("settings2")},
-				{"OnOff", []byte("settings"), nil},
-				{"OffOn", nil, []byte("settings")},
-				// The empty settings value should not be mistaken for ALPS not
-				// being negotiated.
-				{"OnEmpty", []byte("settings"), []byte{}},
-				{"EmptyOn", []byte{}, []byte("settings")},
-				{"EmptyOff", []byte{}, nil},
-				{"OffEmpty", nil, []byte{}},
-			}
-			for _, test := range alpsMismatchTests {
-				flags := []string{"-on-resume-expect-early-data-reason", "alps_mismatch"}
-				if test.initialSettings != nil {
-					flags = append(flags, "-on-initial-application-settings", "proto,"+string(test.initialSettings))
-					flags = append(flags, "-on-initial-expect-peer-application-settings", "runner")
-				}
-				if test.resumeSettings != nil {
-					flags = append(flags, "-on-resume-application-settings", "proto,"+string(test.resumeSettings))
-					flags = append(flags, "-on-resume-expect-peer-application-settings", "runner")
-				}
-
-				// The client should not offer early data.
+				// Test that the server rejects an unexpected EncryptedExtensions message.
 				testCases = append(testCases, testCase{
-					testType: clientTest,
-					name:     fmt.Sprintf("ALPS-EarlyData-Mismatch-%s-Client-%s", test.name, ver.name),
+					protocol: protocol,
+					testType: serverTest,
+					name:     "UnexpectedClientEncryptedExtensions-" + suffix,
+					config: Config{
+						MaxVersion: ver.version,
+						Bugs: ProtocolBugs{
+							AlwaysSendClientEncryptedExtensions: true,
+						},
+					},
+					shouldFail:         true,
+					expectedError:      ":UNEXPECTED_MESSAGE:",
+					expectedLocalError: "remote error: unexpected message",
+				})
+
+				// Test that the server rejects an unexpected extension in an
+				// expected EncryptedExtensions message.
+				testCases = append(testCases, testCase{
+					protocol:           protocol,
+					testType:           serverTest,
+					name:               "ExtraClientEncryptedExtension-" + suffix,
+					skipQUICALPNConfig: true,
 					config: Config{
 						MaxVersion:          ver.version,
-						MaxEarlyDataSize:    16384,
 						NextProtos:          []string{"proto"},
 						ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
+						Bugs: ProtocolBugs{
+							SendExtraClientEncryptedExtension: true,
+						},
+					},
+					flags: []string{
+						"-select-alpn", "proto",
+						"-application-settings", "proto,shim",
+					},
+					// The runner is a client, so it only processes the shim's alert
+					// after checking connection state.
+					expectations: connectionExpectations{
+						peerApplicationSettings: []byte("shim"),
+					},
+					shouldFail:         true,
+					expectedError:      ":UNEXPECTED_EXTENSION:",
+					expectedLocalError: "remote error: unsupported extension",
+				})
+
+				// Test that ALPS is carried over on 0-RTT.
+				for _, empty := range []bool{false, true} {
+					maybeEmpty := ""
+					runnerSettings := "runner"
+					shimSettings := "shim"
+					if empty {
+						maybeEmpty = "Empty-"
+						runnerSettings = ""
+						shimSettings = ""
+					}
+
+					testCases = append(testCases, testCase{
+						protocol:           protocol,
+						testType:           clientTest,
+						name:               "ALPS-EarlyData-Client-" + maybeEmpty + suffix,
+						skipQUICALPNConfig: true,
+						config: Config{
+							MaxVersion:          ver.version,
+							NextProtos:          []string{"proto"},
+							ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
+						},
+						resumeSession: true,
+						earlyData:     true,
+						flags: []string{
+							"-advertise-alpn", "\x05proto",
+							"-expect-alpn", "proto",
+							"-application-settings", "proto," + shimSettings,
+							"-expect-peer-application-settings", runnerSettings,
+						},
+						expectations: connectionExpectations{
+							peerApplicationSettings: []byte(shimSettings),
+						},
+					})
+					testCases = append(testCases, testCase{
+						protocol:           protocol,
+						testType:           serverTest,
+						name:               "ALPS-EarlyData-Server-" + maybeEmpty + suffix,
+						skipQUICALPNConfig: true,
+						config: Config{
+							MaxVersion:          ver.version,
+							NextProtos:          []string{"proto"},
+							ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
+						},
+						resumeSession: true,
+						earlyData:     true,
+						flags: []string{
+							"-select-alpn", "proto",
+							"-application-settings", "proto," + shimSettings,
+							"-expect-peer-application-settings", runnerSettings,
+						},
+						expectations: connectionExpectations{
+							peerApplicationSettings: []byte(shimSettings),
+						},
+					})
+
+					// Sending application settings in 0-RTT handshakes is forbidden.
+					testCases = append(testCases, testCase{
+						protocol:           protocol,
+						testType:           clientTest,
+						name:               "ALPS-EarlyData-SendApplicationSettingsWithEarlyData-Client-" + maybeEmpty + suffix,
+						skipQUICALPNConfig: true,
+						config: Config{
+							MaxVersion:          ver.version,
+							NextProtos:          []string{"proto"},
+							ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
+							Bugs: ProtocolBugs{
+								SendApplicationSettingsWithEarlyData: true,
+							},
+						},
+						resumeSession: true,
+						earlyData:     true,
+						flags: []string{
+							"-advertise-alpn", "\x05proto",
+							"-expect-alpn", "proto",
+							"-application-settings", "proto," + shimSettings,
+							"-expect-peer-application-settings", runnerSettings,
+						},
+						expectations: connectionExpectations{
+							peerApplicationSettings: []byte(shimSettings),
+						},
+						shouldFail:         true,
+						expectedError:      ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:",
+						expectedLocalError: "remote error: illegal parameter",
+					})
+					testCases = append(testCases, testCase{
+						protocol:           protocol,
+						testType:           serverTest,
+						name:               "ALPS-EarlyData-SendApplicationSettingsWithEarlyData-Server-" + maybeEmpty + suffix,
+						skipQUICALPNConfig: true,
+						config: Config{
+							MaxVersion:          ver.version,
+							NextProtos:          []string{"proto"},
+							ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
+							Bugs: ProtocolBugs{
+								SendApplicationSettingsWithEarlyData: true,
+							},
+						},
+						resumeSession: true,
+						earlyData:     true,
+						flags: []string{
+							"-select-alpn", "proto",
+							"-application-settings", "proto," + shimSettings,
+							"-expect-peer-application-settings", runnerSettings,
+						},
+						expectations: connectionExpectations{
+							peerApplicationSettings: []byte(shimSettings),
+						},
+						shouldFail:         true,
+						expectedError:      ":UNEXPECTED_MESSAGE:",
+						expectedLocalError: "remote error: unexpected message",
+					})
+				}
+
+				// Test that the client and server each decline early data if local
+				// ALPS preferences has changed for the current connection.
+				alpsMismatchTests := []struct {
+					name                            string
+					initialSettings, resumeSettings []byte
+				}{
+					{"DifferentValues", []byte("settings1"), []byte("settings2")},
+					{"OnOff", []byte("settings"), nil},
+					{"OffOn", nil, []byte("settings")},
+					// The empty settings value should not be mistaken for ALPS not
+					// being negotiated.
+					{"OnEmpty", []byte("settings"), []byte{}},
+					{"EmptyOn", []byte{}, []byte("settings")},
+					{"EmptyOff", []byte{}, nil},
+					{"OffEmpty", nil, []byte{}},
+				}
+				for _, test := range alpsMismatchTests {
+					flags := []string{"-on-resume-expect-early-data-reason", "alps_mismatch"}
+					if test.initialSettings != nil {
+						flags = append(flags, "-on-initial-application-settings", "proto,"+string(test.initialSettings))
+						flags = append(flags, "-on-initial-expect-peer-application-settings", "runner")
+					}
+					if test.resumeSettings != nil {
+						flags = append(flags, "-on-resume-application-settings", "proto,"+string(test.resumeSettings))
+						flags = append(flags, "-on-resume-expect-peer-application-settings", "runner")
+					}
+
+					// The client should not offer early data.
+					testCases = append(testCases, testCase{
+						protocol:           protocol,
+						testType:           clientTest,
+						name:               fmt.Sprintf("ALPS-EarlyData-Mismatch-%s-Client-%s", test.name, suffix),
+						skipQUICALPNConfig: true,
+						config: Config{
+							MaxVersion:          ver.version,
+							MaxEarlyDataSize:    16384,
+							NextProtos:          []string{"proto"},
+							ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
+						},
+						resumeSession: true,
+						flags: append([]string{
+							"-enable-early-data",
+							"-expect-ticket-supports-early-data",
+							"-expect-no-offer-early-data",
+							"-advertise-alpn", "\x05proto",
+							"-expect-alpn", "proto",
+						}, flags...),
+						expectations: connectionExpectations{
+							peerApplicationSettings: test.initialSettings,
+						},
+						resumeExpectations: &connectionExpectations{
+							peerApplicationSettings: test.resumeSettings,
+						},
+					})
+
+					// The server should reject early data.
+					testCases = append(testCases, testCase{
+						protocol:           protocol,
+						testType:           serverTest,
+						name:               fmt.Sprintf("ALPS-EarlyData-Mismatch-%s-Server-%s", test.name, suffix),
+						skipQUICALPNConfig: true,
+						config: Config{
+							MaxVersion:          ver.version,
+							NextProtos:          []string{"proto"},
+							ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
+						},
+						resumeSession:           true,
+						earlyData:               true,
+						expectEarlyDataRejected: true,
+						flags: append([]string{
+							"-select-alpn", "proto",
+						}, flags...),
+						expectations: connectionExpectations{
+							peerApplicationSettings: test.initialSettings,
+						},
+						resumeExpectations: &connectionExpectations{
+							peerApplicationSettings: test.resumeSettings,
+						},
+					})
+				}
+			} else {
+				// Test the client rejects the ALPS extension if the server
+				// negotiated TLS 1.2 or below.
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: clientTest,
+					name:     "ALPS-Reject-Client-" + suffix,
+					config: Config{
+						MaxVersion:          ver.version,
+						NextProtos:          []string{"foo"},
+						ApplicationSettings: map[string][]byte{"foo": []byte("runner")},
+						Bugs: ProtocolBugs{
+							AlwaysNegotiateApplicationSettings: true,
+						},
+					},
+					flags: []string{
+						"-advertise-alpn", "\x03foo",
+						"-expect-alpn", "foo",
+						"-application-settings", "foo,shim",
+					},
+					shouldFail:         true,
+					expectedError:      ":UNEXPECTED_EXTENSION:",
+					expectedLocalError: "remote error: unsupported extension",
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: clientTest,
+					name:     "ALPS-Reject-Client-Resume-" + suffix,
+					config: Config{
+						MaxVersion: ver.version,
+					},
+					resumeConfig: &Config{
+						MaxVersion:          ver.version,
+						NextProtos:          []string{"foo"},
+						ApplicationSettings: map[string][]byte{"foo": []byte("runner")},
+						Bugs: ProtocolBugs{
+							AlwaysNegotiateApplicationSettings: true,
+						},
 					},
 					resumeSession: true,
-					flags: append([]string{
-						"-enable-early-data",
-						"-expect-ticket-supports-early-data",
-						"-expect-no-offer-early-data",
-						"-advertise-alpn", "\x05proto",
-						"-expect-alpn", "proto",
-					}, flags...),
-					expectations: connectionExpectations{
-						peerApplicationSettings: test.initialSettings,
+					flags: []string{
+						"-on-resume-advertise-alpn", "\x03foo",
+						"-on-resume-expect-alpn", "foo",
+						"-on-resume-application-settings", "foo,shim",
 					},
-					resumeExpectations: &connectionExpectations{
-						peerApplicationSettings: test.resumeSettings,
-					},
+					shouldFail:         true,
+					expectedError:      ":UNEXPECTED_EXTENSION:",
+					expectedLocalError: "remote error: unsupported extension",
 				})
 
-				// The server should reject early data.
+				// Test the server declines ALPS if it negotiates TLS 1.2 or below.
 				testCases = append(testCases, testCase{
+					protocol: protocol,
 					testType: serverTest,
-					name:     fmt.Sprintf("ALPS-EarlyData-Mismatch-%s-Server-%s", test.name, ver.name),
+					name:     "ALPS-Decline-Server-" + suffix,
 					config: Config{
 						MaxVersion:          ver.version,
-						NextProtos:          []string{"proto"},
-						ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
+						NextProtos:          []string{"foo"},
+						ApplicationSettings: map[string][]byte{"foo": []byte("runner")},
 					},
-					resumeSession:           true,
-					earlyData:               true,
-					expectEarlyDataRejected: true,
-					flags: append([]string{
-						"-select-alpn", "proto",
-					}, flags...),
-					expectations: connectionExpectations{
-						peerApplicationSettings: test.initialSettings,
+					// Test both TLS 1.2 full and resumption handshakes.
+					resumeSession: true,
+					flags: []string{
+						"-select-alpn", "foo",
+						"-application-settings", "foo,shim",
 					},
-					resumeExpectations: &connectionExpectations{
-						peerApplicationSettings: test.resumeSettings,
-					},
+					// If not specified, runner and shim both implicitly expect ALPS
+					// is not negotiated.
 				})
 			}
-		} else {
-			// Test the client rejects the ALPS extension if the server
-			// negotiated TLS 1.2 or below.
+
+			// Test Token Binding.
+			if protocol != dtls {
+				const maxTokenBindingVersion = 16
+				const minTokenBindingVersion = 13
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: serverTest,
+					name:     "TokenBinding-Server-" + suffix,
+
+					config: Config{
+						MinVersion:          ver.version,
+						MaxVersion:          ver.version,
+						TokenBindingParams:  []byte{0, 1, 2},
+						TokenBindingVersion: maxTokenBindingVersion,
+					},
+					expectations: connectionExpectations{
+						tokenBinding:      true,
+						tokenBindingParam: 2,
+					},
+					flags: []string{
+						"-token-binding-params",
+						base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+						"-expect-token-binding-param",
+						"2",
+					},
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: serverTest,
+					name:     "TokenBinding-Server-UnsupportedParam-" + suffix,
+
+					config: Config{
+						MinVersion:          ver.version,
+						MaxVersion:          ver.version,
+						TokenBindingParams:  []byte{3},
+						TokenBindingVersion: maxTokenBindingVersion,
+					},
+					flags: []string{
+						"-token-binding-params",
+						base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+					},
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: serverTest,
+					name:     "TokenBinding-Server-OldVersion-" + suffix,
+
+					config: Config{
+						MinVersion:          ver.version,
+						MaxVersion:          ver.version,
+						TokenBindingParams:  []byte{0, 1, 2},
+						TokenBindingVersion: minTokenBindingVersion - 1,
+					},
+					flags: []string{
+						"-token-binding-params",
+						base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+					},
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: serverTest,
+					name:     "TokenBinding-Server-NewVersion-" + suffix,
+
+					config: Config{
+						MinVersion:          ver.version,
+						MaxVersion:          ver.version,
+						TokenBindingParams:  []byte{0, 1, 2},
+						TokenBindingVersion: maxTokenBindingVersion + 1,
+					},
+					expectations: connectionExpectations{
+						tokenBinding:      true,
+						tokenBindingParam: 2,
+					},
+					flags: []string{
+						"-token-binding-params",
+						base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+						"-expect-token-binding-param",
+						"2",
+					},
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: serverTest,
+					name:     "TokenBinding-Server-NoParams-" + suffix,
+
+					config: Config{
+						MinVersion:          ver.version,
+						MaxVersion:          ver.version,
+						TokenBindingParams:  []byte{},
+						TokenBindingVersion: maxTokenBindingVersion,
+					},
+					flags: []string{
+						"-token-binding-params",
+						base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+					},
+					shouldFail:    true,
+					expectedError: ":ERROR_PARSING_EXTENSION:",
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: serverTest,
+					name:     "TokenBinding-Server-RepeatedParam" + suffix,
+
+					config: Config{
+						MinVersion:          ver.version,
+						MaxVersion:          ver.version,
+						TokenBindingParams:  []byte{0, 1, 2, 2},
+						TokenBindingVersion: maxTokenBindingVersion,
+					},
+					expectations: connectionExpectations{
+						tokenBinding:      true,
+						tokenBindingParam: 2,
+					},
+					flags: []string{
+						"-token-binding-params",
+						base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+						"-expect-token-binding-param",
+						"2",
+					},
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: clientTest,
+					name:     "TokenBinding-Client-" + suffix,
+
+					config: Config{
+						MinVersion:               ver.version,
+						MaxVersion:               ver.version,
+						TokenBindingParams:       []byte{2},
+						TokenBindingVersion:      maxTokenBindingVersion,
+						ExpectTokenBindingParams: []byte{0, 1, 2},
+					},
+					flags: []string{
+						"-token-binding-params",
+						base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
+						"-expect-token-binding-param",
+						"2",
+					},
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: clientTest,
+					name:     "TokenBinding-Client-Unexpected-" + suffix,
+
+					config: Config{
+						MinVersion:          ver.version,
+						MaxVersion:          ver.version,
+						TokenBindingParams:  []byte{2},
+						TokenBindingVersion: maxTokenBindingVersion,
+					},
+					shouldFail:    true,
+					expectedError: ":UNEXPECTED_EXTENSION:",
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: clientTest,
+					name:     "TokenBinding-Client-ExtraParams-" + suffix,
+
+					config: Config{
+						MinVersion:               ver.version,
+						MaxVersion:               ver.version,
+						TokenBindingParams:       []byte{2, 1},
+						TokenBindingVersion:      maxTokenBindingVersion,
+						ExpectTokenBindingParams: []byte{0, 1, 2},
+					},
+					flags: []string{
+						"-token-binding-params",
+						base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
+						"-expect-token-binding-param",
+						"2",
+					},
+					shouldFail:    true,
+					expectedError: ":ERROR_PARSING_EXTENSION:",
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: clientTest,
+					name:     "TokenBinding-Client-NoParams-" + suffix,
+
+					config: Config{
+						MinVersion:               ver.version,
+						MaxVersion:               ver.version,
+						TokenBindingParams:       []byte{},
+						TokenBindingVersion:      maxTokenBindingVersion,
+						ExpectTokenBindingParams: []byte{0, 1, 2},
+					},
+					flags: []string{
+						"-token-binding-params",
+						base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
+						"-expect-token-binding-param",
+						"2",
+					},
+					shouldFail:    true,
+					expectedError: ":ERROR_PARSING_EXTENSION:",
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: clientTest,
+					name:     "TokenBinding-Client-WrongParam-" + suffix,
+
+					config: Config{
+						MinVersion:               ver.version,
+						MaxVersion:               ver.version,
+						TokenBindingParams:       []byte{3},
+						TokenBindingVersion:      maxTokenBindingVersion,
+						ExpectTokenBindingParams: []byte{0, 1, 2},
+					},
+					flags: []string{
+						"-token-binding-params",
+						base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
+						"-expect-token-binding-param",
+						"2",
+					},
+					shouldFail:    true,
+					expectedError: ":ERROR_PARSING_EXTENSION:",
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: clientTest,
+					name:     "TokenBinding-Client-OldVersion-" + suffix,
+
+					config: Config{
+						MinVersion:               ver.version,
+						MaxVersion:               ver.version,
+						TokenBindingParams:       []byte{2},
+						TokenBindingVersion:      minTokenBindingVersion - 1,
+						ExpectTokenBindingParams: []byte{0, 1, 2},
+					},
+					flags: []string{
+						"-token-binding-params",
+						base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
+					},
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: clientTest,
+					name:     "TokenBinding-Client-MinVersion-" + suffix,
+
+					config: Config{
+						MinVersion:               ver.version,
+						MaxVersion:               ver.version,
+						TokenBindingParams:       []byte{2},
+						TokenBindingVersion:      minTokenBindingVersion,
+						ExpectTokenBindingParams: []byte{0, 1, 2},
+					},
+					flags: []string{
+						"-token-binding-params",
+						base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
+						"-expect-token-binding-param",
+						"2",
+					},
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: clientTest,
+					name:     "TokenBinding-Client-VersionTooNew-" + suffix,
+
+					config: Config{
+						MinVersion:               ver.version,
+						MaxVersion:               ver.version,
+						TokenBindingParams:       []byte{2},
+						TokenBindingVersion:      maxTokenBindingVersion + 1,
+						ExpectTokenBindingParams: []byte{0, 1, 2},
+					},
+					flags: []string{
+						"-token-binding-params",
+						base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
+					},
+					shouldFail:    true,
+					expectedError: "ERROR_PARSING_EXTENSION",
+				})
+				if ver.version < VersionTLS13 {
+					testCases = append(testCases, testCase{
+						protocol: protocol,
+						testType: clientTest,
+						name:     "TokenBinding-Client-NoEMS-" + suffix,
+
+						config: Config{
+							MinVersion:               ver.version,
+							MaxVersion:               ver.version,
+							TokenBindingParams:       []byte{2},
+							TokenBindingVersion:      maxTokenBindingVersion,
+							ExpectTokenBindingParams: []byte{2, 1, 0},
+							Bugs: ProtocolBugs{
+								NoExtendedMasterSecret: true,
+							},
+						},
+						flags: []string{
+							"-token-binding-params",
+							base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+						},
+						shouldFail:    true,
+						expectedError: ":NEGOTIATED_TB_WITHOUT_EMS_OR_RI:",
+					})
+					testCases = append(testCases, testCase{
+						protocol: protocol,
+						testType: serverTest,
+						name:     "TokenBinding-Server-NoEMS-" + suffix,
+
+						config: Config{
+							MinVersion:          ver.version,
+							MaxVersion:          ver.version,
+							TokenBindingParams:  []byte{0, 1, 2},
+							TokenBindingVersion: maxTokenBindingVersion,
+							Bugs: ProtocolBugs{
+								NoExtendedMasterSecret: true,
+							},
+						},
+						flags: []string{
+							"-token-binding-params",
+							base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+						},
+						shouldFail:    true,
+						expectedError: ":NEGOTIATED_TB_WITHOUT_EMS_OR_RI:",
+					})
+					testCases = append(testCases, testCase{
+						protocol: protocol,
+						testType: clientTest,
+						name:     "TokenBinding-Client-NoRI-" + suffix,
+
+						config: Config{
+							MinVersion:               ver.version,
+							MaxVersion:               ver.version,
+							TokenBindingParams:       []byte{2},
+							TokenBindingVersion:      maxTokenBindingVersion,
+							ExpectTokenBindingParams: []byte{2, 1, 0},
+							Bugs: ProtocolBugs{
+								NoRenegotiationInfo: true,
+							},
+						},
+						flags: []string{
+							"-token-binding-params",
+							base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+						},
+						shouldFail:    true,
+						expectedError: ":NEGOTIATED_TB_WITHOUT_EMS_OR_RI:",
+					})
+					testCases = append(testCases, testCase{
+						protocol: protocol,
+						testType: serverTest,
+						name:     "TokenBinding-Server-NoRI-" + suffix,
+
+						config: Config{
+							MinVersion:          ver.version,
+							MaxVersion:          ver.version,
+							TokenBindingParams:  []byte{0, 1, 2},
+							TokenBindingVersion: maxTokenBindingVersion,
+							Bugs: ProtocolBugs{
+								NoRenegotiationInfo: true,
+							},
+						},
+						flags: []string{
+							"-token-binding-params",
+							base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+						},
+						shouldFail:    true,
+						expectedError: ":NEGOTIATED_TB_WITHOUT_EMS_OR_RI:",
+					})
+				} else {
+					testCases = append(testCases, testCase{
+						protocol: protocol,
+						testType: clientTest,
+						name:     "TokenBinding-WithEarlyDataFails-" + suffix,
+						config: Config{
+							MinVersion:               ver.version,
+							MaxVersion:               ver.version,
+							TokenBindingParams:       []byte{2},
+							TokenBindingVersion:      maxTokenBindingVersion,
+							ExpectTokenBindingParams: []byte{2, 1, 0},
+						},
+						resumeSession: true,
+						earlyData:     true,
+						flags: []string{
+							"-token-binding-params",
+							base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+						},
+						shouldFail:    true,
+						expectedError: ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:",
+					})
+					testCases = append(testCases, testCase{
+						protocol: protocol,
+						testType: serverTest,
+						name:     "TokenBinding-EarlyDataRejected-" + suffix,
+						config: Config{
+							MinVersion:          ver.version,
+							MaxVersion:          ver.version,
+							TokenBindingParams:  []byte{0, 1, 2},
+							TokenBindingVersion: maxTokenBindingVersion,
+						},
+						resumeSession:           true,
+						earlyData:               true,
+						expectEarlyDataRejected: true,
+						expectations: connectionExpectations{
+							tokenBinding:      true,
+							tokenBindingParam: 2,
+						},
+						flags: []string{
+							"-token-binding-params",
+							base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+							"-on-retry-expect-early-data-reason", "token_binding",
+						},
+					})
+				}
+			}
+
+			// Test QUIC transport params
+			if protocol == quic {
+				// Client sends params
+				testCases = append(testCases, testCase{
+					testType: clientTest,
+					protocol: protocol,
+					name:     "QUICTransportParams-Client-" + suffix,
+					config: Config{
+						MinVersion:          ver.version,
+						MaxVersion:          ver.version,
+						QUICTransportParams: []byte{1, 2},
+					},
+					flags: []string{
+						"-quic-transport-params",
+						base64.StdEncoding.EncodeToString([]byte{3, 4}),
+						"-expect-quic-transport-params",
+						base64.StdEncoding.EncodeToString([]byte{1, 2}),
+					},
+					expectations: connectionExpectations{
+						quicTransportParams: []byte{3, 4},
+					},
+					skipTransportParamsConfig: true,
+				})
+				testCases = append(testCases, testCase{
+					testType: clientTest,
+					protocol: protocol,
+					name:     "QUICTransportParams-Client-RejectMissing-" + suffix,
+					config: Config{
+						MinVersion: ver.version,
+						MaxVersion: ver.version,
+					},
+					flags: []string{
+						"-quic-transport-params",
+						base64.StdEncoding.EncodeToString([]byte{3, 4}),
+					},
+					shouldFail:                true,
+					expectedError:             ":MISSING_EXTENSION:",
+					skipTransportParamsConfig: true,
+				})
+				// Server sends params
+				testCases = append(testCases, testCase{
+					testType: serverTest,
+					protocol: protocol,
+					name:     "QUICTransportParams-Server-" + suffix,
+					config: Config{
+						MinVersion:          ver.version,
+						MaxVersion:          ver.version,
+						QUICTransportParams: []byte{1, 2},
+					},
+					flags: []string{
+						"-quic-transport-params",
+						base64.StdEncoding.EncodeToString([]byte{3, 4}),
+						"-expect-quic-transport-params",
+						base64.StdEncoding.EncodeToString([]byte{1, 2}),
+					},
+					expectations: connectionExpectations{
+						quicTransportParams: []byte{3, 4},
+					},
+					skipTransportParamsConfig: true,
+				})
+				testCases = append(testCases, testCase{
+					testType: serverTest,
+					protocol: protocol,
+					name:     "QUICTransportParams-Server-RejectMissing-" + suffix,
+					config: Config{
+						MinVersion: ver.version,
+						MaxVersion: ver.version,
+					},
+					flags: []string{
+						"-quic-transport-params",
+						base64.StdEncoding.EncodeToString([]byte{3, 4}),
+					},
+					expectations: connectionExpectations{
+						quicTransportParams: []byte{3, 4},
+					},
+					shouldFail:                true,
+					expectedError:             ":MISSING_EXTENSION:",
+					skipTransportParamsConfig: true,
+				})
+			} else {
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: clientTest,
+					name:     "QUICTransportParams-Client-NotSentInNonQUIC-" + suffix,
+					config: Config{
+						MinVersion: ver.version,
+						MaxVersion: ver.version,
+					},
+					flags: []string{
+						"-max-version",
+						strconv.Itoa(int(ver.versionWire)),
+						"-quic-transport-params",
+						base64.StdEncoding.EncodeToString([]byte{3, 4}),
+					},
+					shouldFail:                true,
+					expectedError:             ":QUIC_TRANSPORT_PARAMETERS_MISCONFIGURED:",
+					skipTransportParamsConfig: true,
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: serverTest,
+					name:     "QUICTransportParams-Server-RejectedInNonQUIC-" + suffix,
+					config: Config{
+						MinVersion:          ver.version,
+						MaxVersion:          ver.version,
+						QUICTransportParams: []byte{1, 2},
+					},
+					flags: []string{
+						"-expect-quic-transport-params",
+						base64.StdEncoding.EncodeToString([]byte{1, 2}),
+					},
+					shouldFail:                true,
+					expectedLocalError:        "remote error: unsupported extension",
+					skipTransportParamsConfig: true,
+				})
+			}
+
+			// Test ticket behavior.
+
+			// Resume with a corrupt ticket.
 			testCases = append(testCases, testCase{
-				testType: clientTest,
-				name:     "ALPS-Reject-Client-" + ver.name,
+				protocol: protocol,
+				testType: serverTest,
+				name:     "CorruptTicket-" + suffix,
 				config: Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"foo"},
-					ApplicationSettings: map[string][]byte{"foo": []byte("runner")},
+					MaxVersion: ver.version,
 					Bugs: ProtocolBugs{
-						AlwaysNegotiateApplicationSettings: true,
+						FilterTicket: func(in []byte) ([]byte, error) {
+							in[len(in)-1] ^= 1
+							return in, nil
+						},
 					},
 				},
-				flags: []string{
-					"-advertise-alpn", "\x03foo",
-					"-expect-alpn", "foo",
-					"-application-settings", "foo,shim",
-				},
-				shouldFail:         true,
-				expectedError:      ":UNEXPECTED_EXTENSION:",
-				expectedLocalError: "remote error: unsupported extension",
+				resumeSession:        true,
+				expectResumeRejected: true,
 			})
+			// Test the ticket callback, with and without renewal.
 			testCases = append(testCases, testCase{
-				testType: clientTest,
-				name:     "ALPS-Reject-Client-Resume-" + ver.name,
+				protocol: protocol,
+				testType: serverTest,
+				name:     "TicketCallback-" + suffix,
 				config: Config{
 					MaxVersion: ver.version,
 				},
-				resumeConfig: &Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"foo"},
-					ApplicationSettings: map[string][]byte{"foo": []byte("runner")},
+				resumeSession: true,
+				flags:         []string{"-use-ticket-callback"},
+			})
+			testCases = append(testCases, testCase{
+				protocol: protocol,
+				testType: serverTest,
+				name:     "TicketCallback-Renew-" + suffix,
+				config: Config{
+					MaxVersion: ver.version,
 					Bugs: ProtocolBugs{
-						AlwaysNegotiateApplicationSettings: true,
+						ExpectNewTicket: true,
 					},
 				},
+				flags:         []string{"-use-ticket-callback", "-renew-ticket"},
+				resumeSession: true,
+			})
+
+			// Test that the ticket callback is only called once when everything before
+			// it in the ClientHello is asynchronous. This corrupts the ticket so
+			// certificate selection callbacks run.
+			testCases = append(testCases, testCase{
+				protocol: protocol,
+				testType: serverTest,
+				name:     "TicketCallback-SingleCall-" + suffix,
+				config: Config{
+					MaxVersion: ver.version,
+					Bugs: ProtocolBugs{
+						FilterTicket: func(in []byte) ([]byte, error) {
+							in[len(in)-1] ^= 1
+							return in, nil
+						},
+					},
+				},
+				resumeSession:        true,
+				expectResumeRejected: true,
+				flags: []string{
+					"-use-ticket-callback",
+					"-async",
+				},
+			})
+
+			// Resume with various lengths of ticket session id.
+			if ver.version < VersionTLS13 {
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: serverTest,
+					name:     "TicketSessionIDLength-0-" + suffix,
+					config: Config{
+						MaxVersion: ver.version,
+						Bugs: ProtocolBugs{
+							EmptyTicketSessionID: true,
+						},
+					},
+					resumeSession: true,
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: serverTest,
+					name:     "TicketSessionIDLength-16-" + suffix,
+					config: Config{
+						MaxVersion: ver.version,
+						Bugs: ProtocolBugs{
+							TicketSessionIDLength: 16,
+						},
+					},
+					resumeSession: true,
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: serverTest,
+					name:     "TicketSessionIDLength-32-" + suffix,
+					config: Config{
+						MaxVersion: ver.version,
+						Bugs: ProtocolBugs{
+							TicketSessionIDLength: 32,
+						},
+					},
+					resumeSession: true,
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: serverTest,
+					name:     "TicketSessionIDLength-33-" + suffix,
+					config: Config{
+						MaxVersion: ver.version,
+						Bugs: ProtocolBugs{
+							TicketSessionIDLength: 33,
+						},
+					},
+					resumeSession: true,
+					shouldFail:    true,
+					// The maximum session ID length is 32.
+					expectedError: ":DECODE_ERROR:",
+				})
+			}
+
+			// Basic DTLS-SRTP tests. Include fake profiles to ensure they
+			// are ignored.
+			if protocol == dtls {
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					name:     "SRTP-Client-" + suffix,
+					config: Config{
+						MaxVersion:             ver.version,
+						SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42},
+					},
+					flags: []string{
+						"-srtp-profiles",
+						"SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
+					},
+					expectations: connectionExpectations{
+						srtpProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80,
+					},
+				})
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: serverTest,
+					name:     "SRTP-Server-" + suffix,
+					config: Config{
+						MaxVersion:             ver.version,
+						SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42},
+					},
+					flags: []string{
+						"-srtp-profiles",
+						"SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
+					},
+					expectations: connectionExpectations{
+						srtpProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80,
+					},
+				})
+				// Test that the MKI is ignored.
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: serverTest,
+					name:     "SRTP-Server-IgnoreMKI-" + suffix,
+					config: Config{
+						MaxVersion:             ver.version,
+						SRTPProtectionProfiles: []uint16{SRTP_AES128_CM_HMAC_SHA1_80},
+						Bugs: ProtocolBugs{
+							SRTPMasterKeyIdentifer: "bogus",
+						},
+					},
+					flags: []string{
+						"-srtp-profiles",
+						"SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
+					},
+					expectations: connectionExpectations{
+						srtpProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80,
+					},
+				})
+				// Test that SRTP isn't negotiated on the server if there were
+				// no matching profiles.
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					testType: serverTest,
+					name:     "SRTP-Server-NoMatch-" + suffix,
+					config: Config{
+						MaxVersion:             ver.version,
+						SRTPProtectionProfiles: []uint16{100, 101, 102},
+					},
+					flags: []string{
+						"-srtp-profiles",
+						"SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
+					},
+					expectations: connectionExpectations{
+						srtpProtectionProfile: 0,
+					},
+				})
+				// Test that the server returning an invalid SRTP profile is
+				// flagged as an error by the client.
+				testCases = append(testCases, testCase{
+					protocol: protocol,
+					name:     "SRTP-Client-NoMatch-" + suffix,
+					config: Config{
+						MaxVersion: ver.version,
+						Bugs: ProtocolBugs{
+							SendSRTPProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_32,
+						},
+					},
+					flags: []string{
+						"-srtp-profiles",
+						"SRTP_AES128_CM_SHA1_80",
+					},
+					shouldFail:    true,
+					expectedError: ":BAD_SRTP_PROTECTION_PROFILE_LIST:",
+				})
+			}
+
+			// Test SCT list.
+			testCases = append(testCases, testCase{
+				protocol: protocol,
+				name:     "SignedCertificateTimestampList-Client-" + suffix,
+				testType: clientTest,
+				config: Config{
+					MaxVersion: ver.version,
+				},
+				flags: []string{
+					"-enable-signed-cert-timestamps",
+					"-expect-signed-cert-timestamps",
+					base64.StdEncoding.EncodeToString(testSCTList),
+				},
 				resumeSession: true,
-				flags: []string{
-					"-on-resume-advertise-alpn", "\x03foo",
-					"-on-resume-expect-alpn", "foo",
-					"-on-resume-application-settings", "foo,shim",
-				},
-				shouldFail:         true,
-				expectedError:      ":UNEXPECTED_EXTENSION:",
-				expectedLocalError: "remote error: unsupported extension",
 			})
 
-			// Test the server declines ALPS if it negotiates TLS 1.2 or below.
+			var differentSCTList []byte
+			differentSCTList = append(differentSCTList, testSCTList...)
+			differentSCTList[len(differentSCTList)-1] ^= 1
+
+			// The SCT extension did not specify that it must only be sent on resumption as it
+			// should have, so test that we tolerate but ignore it.
 			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "ALPS-Decline-Server-" + ver.name,
+				protocol: protocol,
+				name:     "SendSCTListOnResume-" + suffix,
 				config: Config{
-					MaxVersion:          ver.version,
-					NextProtos:          []string{"foo"},
-					ApplicationSettings: map[string][]byte{"foo": []byte("runner")},
-				},
-				// Test both TLS 1.2 full and resumption handshakes.
-				resumeSession: true,
-				flags: []string{
-					"-select-alpn", "foo",
-					"-application-settings", "foo,shim",
-				},
-				// If not specified, runner and shim both implicitly expect ALPS
-				// is not negotiated.
-			})
-		}
-
-		// Test Token Binding.
-
-		const maxTokenBindingVersion = 16
-		const minTokenBindingVersion = 13
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "TokenBinding-Server-" + ver.name,
-
-			config: Config{
-				MinVersion:          ver.version,
-				MaxVersion:          ver.version,
-				TokenBindingParams:  []byte{0, 1, 2},
-				TokenBindingVersion: maxTokenBindingVersion,
-			},
-			expectations: connectionExpectations{
-				tokenBinding:      true,
-				tokenBindingParam: 2,
-			},
-			flags: []string{
-				"-token-binding-params",
-				base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
-				"-expect-token-binding-param",
-				"2",
-			},
-		})
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "TokenBinding-Server-UnsupportedParam-" + ver.name,
-
-			config: Config{
-				MinVersion:          ver.version,
-				MaxVersion:          ver.version,
-				TokenBindingParams:  []byte{3},
-				TokenBindingVersion: maxTokenBindingVersion,
-			},
-			flags: []string{
-				"-token-binding-params",
-				base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
-			},
-		})
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "TokenBinding-Server-OldVersion-" + ver.name,
-
-			config: Config{
-				MinVersion:          ver.version,
-				MaxVersion:          ver.version,
-				TokenBindingParams:  []byte{0, 1, 2},
-				TokenBindingVersion: minTokenBindingVersion - 1,
-			},
-			flags: []string{
-				"-token-binding-params",
-				base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
-			},
-		})
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "TokenBinding-Server-NewVersion-" + ver.name,
-
-			config: Config{
-				MinVersion:          ver.version,
-				MaxVersion:          ver.version,
-				TokenBindingParams:  []byte{0, 1, 2},
-				TokenBindingVersion: maxTokenBindingVersion + 1,
-			},
-			expectations: connectionExpectations{
-				tokenBinding:      true,
-				tokenBindingParam: 2,
-			},
-			flags: []string{
-				"-token-binding-params",
-				base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
-				"-expect-token-binding-param",
-				"2",
-			},
-		})
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "TokenBinding-Server-NoParams-" + ver.name,
-
-			config: Config{
-				MinVersion:          ver.version,
-				MaxVersion:          ver.version,
-				TokenBindingParams:  []byte{},
-				TokenBindingVersion: maxTokenBindingVersion,
-			},
-			flags: []string{
-				"-token-binding-params",
-				base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
-			},
-			shouldFail:    true,
-			expectedError: ":ERROR_PARSING_EXTENSION:",
-		})
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "TokenBinding-Server-RepeatedParam" + ver.name,
-
-			config: Config{
-				MinVersion:          ver.version,
-				MaxVersion:          ver.version,
-				TokenBindingParams:  []byte{0, 1, 2, 2},
-				TokenBindingVersion: maxTokenBindingVersion,
-			},
-			expectations: connectionExpectations{
-				tokenBinding:      true,
-				tokenBindingParam: 2,
-			},
-			flags: []string{
-				"-token-binding-params",
-				base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
-				"-expect-token-binding-param",
-				"2",
-			},
-		})
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "TokenBinding-Client-" + ver.name,
-
-			config: Config{
-				MinVersion:               ver.version,
-				MaxVersion:               ver.version,
-				TokenBindingParams:       []byte{2},
-				TokenBindingVersion:      maxTokenBindingVersion,
-				ExpectTokenBindingParams: []byte{0, 1, 2},
-			},
-			flags: []string{
-				"-token-binding-params",
-				base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
-				"-expect-token-binding-param",
-				"2",
-			},
-		})
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "TokenBinding-Client-Unexpected-" + ver.name,
-
-			config: Config{
-				MinVersion:          ver.version,
-				MaxVersion:          ver.version,
-				TokenBindingParams:  []byte{2},
-				TokenBindingVersion: maxTokenBindingVersion,
-			},
-			shouldFail:    true,
-			expectedError: ":UNEXPECTED_EXTENSION:",
-		})
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "TokenBinding-Client-ExtraParams-" + ver.name,
-
-			config: Config{
-				MinVersion:               ver.version,
-				MaxVersion:               ver.version,
-				TokenBindingParams:       []byte{2, 1},
-				TokenBindingVersion:      maxTokenBindingVersion,
-				ExpectTokenBindingParams: []byte{0, 1, 2},
-			},
-			flags: []string{
-				"-token-binding-params",
-				base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
-				"-expect-token-binding-param",
-				"2",
-			},
-			shouldFail:    true,
-			expectedError: ":ERROR_PARSING_EXTENSION:",
-		})
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "TokenBinding-Client-NoParams-" + ver.name,
-
-			config: Config{
-				MinVersion:               ver.version,
-				MaxVersion:               ver.version,
-				TokenBindingParams:       []byte{},
-				TokenBindingVersion:      maxTokenBindingVersion,
-				ExpectTokenBindingParams: []byte{0, 1, 2},
-			},
-			flags: []string{
-				"-token-binding-params",
-				base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
-				"-expect-token-binding-param",
-				"2",
-			},
-			shouldFail:    true,
-			expectedError: ":ERROR_PARSING_EXTENSION:",
-		})
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "TokenBinding-Client-WrongParam-" + ver.name,
-
-			config: Config{
-				MinVersion:               ver.version,
-				MaxVersion:               ver.version,
-				TokenBindingParams:       []byte{3},
-				TokenBindingVersion:      maxTokenBindingVersion,
-				ExpectTokenBindingParams: []byte{0, 1, 2},
-			},
-			flags: []string{
-				"-token-binding-params",
-				base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
-				"-expect-token-binding-param",
-				"2",
-			},
-			shouldFail:    true,
-			expectedError: ":ERROR_PARSING_EXTENSION:",
-		})
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "TokenBinding-Client-OldVersion-" + ver.name,
-
-			config: Config{
-				MinVersion:               ver.version,
-				MaxVersion:               ver.version,
-				TokenBindingParams:       []byte{2},
-				TokenBindingVersion:      minTokenBindingVersion - 1,
-				ExpectTokenBindingParams: []byte{0, 1, 2},
-			},
-			flags: []string{
-				"-token-binding-params",
-				base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
-			},
-		})
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "TokenBinding-Client-MinVersion-" + ver.name,
-
-			config: Config{
-				MinVersion:               ver.version,
-				MaxVersion:               ver.version,
-				TokenBindingParams:       []byte{2},
-				TokenBindingVersion:      minTokenBindingVersion,
-				ExpectTokenBindingParams: []byte{0, 1, 2},
-			},
-			flags: []string{
-				"-token-binding-params",
-				base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
-				"-expect-token-binding-param",
-				"2",
-			},
-		})
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "TokenBinding-Client-VersionTooNew-" + ver.name,
-
-			config: Config{
-				MinVersion:               ver.version,
-				MaxVersion:               ver.version,
-				TokenBindingParams:       []byte{2},
-				TokenBindingVersion:      maxTokenBindingVersion + 1,
-				ExpectTokenBindingParams: []byte{0, 1, 2},
-			},
-			flags: []string{
-				"-token-binding-params",
-				base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
-			},
-			shouldFail:    true,
-			expectedError: "ERROR_PARSING_EXTENSION",
-		})
-		if ver.version < VersionTLS13 {
-			testCases = append(testCases, testCase{
-				testType: clientTest,
-				name:     "TokenBinding-Client-NoEMS-" + ver.name,
-
-				config: Config{
-					MinVersion:               ver.version,
-					MaxVersion:               ver.version,
-					TokenBindingParams:       []byte{2},
-					TokenBindingVersion:      maxTokenBindingVersion,
-					ExpectTokenBindingParams: []byte{2, 1, 0},
+					MaxVersion: ver.version,
 					Bugs: ProtocolBugs{
-						NoExtendedMasterSecret: true,
+						SendSCTListOnResume: differentSCTList,
 					},
 				},
 				flags: []string{
-					"-token-binding-params",
-					base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
-				},
-				shouldFail:    true,
-				expectedError: ":NEGOTIATED_TB_WITHOUT_EMS_OR_RI:",
-			})
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "TokenBinding-Server-NoEMS-" + ver.name,
-
-				config: Config{
-					MinVersion:          ver.version,
-					MaxVersion:          ver.version,
-					TokenBindingParams:  []byte{0, 1, 2},
-					TokenBindingVersion: maxTokenBindingVersion,
-					Bugs: ProtocolBugs{
-						NoExtendedMasterSecret: true,
-					},
-				},
-				flags: []string{
-					"-token-binding-params",
-					base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
-				},
-				shouldFail:    true,
-				expectedError: ":NEGOTIATED_TB_WITHOUT_EMS_OR_RI:",
-			})
-			testCases = append(testCases, testCase{
-				testType: clientTest,
-				name:     "TokenBinding-Client-NoRI-" + ver.name,
-
-				config: Config{
-					MinVersion:               ver.version,
-					MaxVersion:               ver.version,
-					TokenBindingParams:       []byte{2},
-					TokenBindingVersion:      maxTokenBindingVersion,
-					ExpectTokenBindingParams: []byte{2, 1, 0},
-					Bugs: ProtocolBugs{
-						NoRenegotiationInfo: true,
-					},
-				},
-				flags: []string{
-					"-token-binding-params",
-					base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
-				},
-				shouldFail:    true,
-				expectedError: ":NEGOTIATED_TB_WITHOUT_EMS_OR_RI:",
-			})
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "TokenBinding-Server-NoRI-" + ver.name,
-
-				config: Config{
-					MinVersion:          ver.version,
-					MaxVersion:          ver.version,
-					TokenBindingParams:  []byte{0, 1, 2},
-					TokenBindingVersion: maxTokenBindingVersion,
-					Bugs: ProtocolBugs{
-						NoRenegotiationInfo: true,
-					},
-				},
-				flags: []string{
-					"-token-binding-params",
-					base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
-				},
-				shouldFail:    true,
-				expectedError: ":NEGOTIATED_TB_WITHOUT_EMS_OR_RI:",
-			})
-		} else {
-			testCases = append(testCases, testCase{
-				testType: clientTest,
-				name:     "TokenBinding-WithEarlyDataFails-" + ver.name,
-				config: Config{
-					MinVersion:               ver.version,
-					MaxVersion:               ver.version,
-					TokenBindingParams:       []byte{2},
-					TokenBindingVersion:      maxTokenBindingVersion,
-					ExpectTokenBindingParams: []byte{2, 1, 0},
+					"-enable-signed-cert-timestamps",
+					"-expect-signed-cert-timestamps",
+					base64.StdEncoding.EncodeToString(testSCTList),
 				},
 				resumeSession: true,
-				earlyData:     true,
-				flags: []string{
-					"-token-binding-params",
-					base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
-				},
-				shouldFail:    true,
-				expectedError: ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:",
 			})
+
 			testCases = append(testCases, testCase{
+				protocol: protocol,
+				name:     "SignedCertificateTimestampList-Server-" + suffix,
 				testType: serverTest,
-				name:     "TokenBinding-EarlyDataRejected-" + ver.name,
 				config: Config{
-					MinVersion:          ver.version,
-					MaxVersion:          ver.version,
-					TokenBindingParams:  []byte{0, 1, 2},
-					TokenBindingVersion: maxTokenBindingVersion,
+					MaxVersion: ver.version,
 				},
-				resumeSession:           true,
-				earlyData:               true,
-				expectEarlyDataRejected: true,
+				flags: []string{
+					"-signed-cert-timestamps",
+					base64.StdEncoding.EncodeToString(testSCTList),
+				},
 				expectations: connectionExpectations{
-					tokenBinding:      true,
-					tokenBindingParam: 2,
+					sctList: testSCTList,
+				},
+				resumeSession: true,
+			})
+
+			emptySCTListCert := *testCerts[0].cert
+			emptySCTListCert.SignedCertificateTimestampList = []byte{0, 0}
+
+			// Test empty SCT list.
+			testCases = append(testCases, testCase{
+				protocol: protocol,
+				name:     "SignedCertificateTimestampListEmpty-Client-" + suffix,
+				testType: clientTest,
+				config: Config{
+					MaxVersion:   ver.version,
+					Certificates: []Certificate{emptySCTListCert},
 				},
 				flags: []string{
-					"-token-binding-params",
-					base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
-					"-on-retry-expect-early-data-reason", "token_binding",
+					"-enable-signed-cert-timestamps",
+				},
+				shouldFail:    true,
+				expectedError: ":ERROR_PARSING_EXTENSION:",
+			})
+
+			emptySCTCert := *testCerts[0].cert
+			emptySCTCert.SignedCertificateTimestampList = []byte{0, 6, 0, 2, 1, 2, 0, 0}
+
+			// Test empty SCT in non-empty list.
+			testCases = append(testCases, testCase{
+				protocol: protocol,
+				name:     "SignedCertificateTimestampListEmptySCT-Client-" + suffix,
+				testType: clientTest,
+				config: Config{
+					MaxVersion:   ver.version,
+					Certificates: []Certificate{emptySCTCert},
+				},
+				flags: []string{
+					"-enable-signed-cert-timestamps",
+				},
+				shouldFail:    true,
+				expectedError: ":ERROR_PARSING_EXTENSION:",
+			})
+
+			// Test that certificate-related extensions are not sent unsolicited.
+			testCases = append(testCases, testCase{
+				protocol: protocol,
+				testType: serverTest,
+				name:     "UnsolicitedCertificateExtensions-" + suffix,
+				config: Config{
+					MaxVersion: ver.version,
+					Bugs: ProtocolBugs{
+						NoOCSPStapling:                true,
+						NoSignedCertificateTimestamps: true,
+					},
+				},
+				flags: []string{
+					"-ocsp-response",
+					base64.StdEncoding.EncodeToString(testOCSPResponse),
+					"-signed-cert-timestamps",
+					base64.StdEncoding.EncodeToString(testSCTList),
 				},
 			})
 		}
-
-		// Test QUIC transport params
-		if ver.version >= VersionTLS13 {
-			// Client sends params
-			testCases = append(testCases, testCase{
-				testType: clientTest,
-				protocol: quic,
-				name:     "QUICTransportParams-Client-" + ver.name,
-				config: Config{
-					MinVersion:          ver.version,
-					MaxVersion:          ver.version,
-					QUICTransportParams: []byte{1, 2},
-				},
-				flags: []string{
-					"-quic-transport-params",
-					base64.StdEncoding.EncodeToString([]byte{3, 4}),
-					"-expect-quic-transport-params",
-					base64.StdEncoding.EncodeToString([]byte{1, 2}),
-				},
-				expectations: connectionExpectations{
-					quicTransportParams: []byte{3, 4},
-				},
-				skipTransportParamsConfig: true,
-			})
-			testCases = append(testCases, testCase{
-				testType: clientTest,
-				protocol: quic,
-				name:     "QUICTransportParams-Client-RejectMissing-" + ver.name,
-				config: Config{
-					MinVersion: ver.version,
-					MaxVersion: ver.version,
-				},
-				flags: []string{
-					"-quic-transport-params",
-					base64.StdEncoding.EncodeToString([]byte{3, 4}),
-				},
-				shouldFail:                true,
-				expectedError:             ":MISSING_EXTENSION:",
-				skipTransportParamsConfig: true,
-			})
-			// Server sends params
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				protocol: quic,
-				name:     "QUICTransportParams-Server-" + ver.name,
-				config: Config{
-					MinVersion:          ver.version,
-					MaxVersion:          ver.version,
-					QUICTransportParams: []byte{1, 2},
-				},
-				flags: []string{
-					"-quic-transport-params",
-					base64.StdEncoding.EncodeToString([]byte{3, 4}),
-					"-expect-quic-transport-params",
-					base64.StdEncoding.EncodeToString([]byte{1, 2}),
-				},
-				expectations: connectionExpectations{
-					quicTransportParams: []byte{3, 4},
-				},
-				skipTransportParamsConfig: true,
-			})
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				protocol: quic,
-				name:     "QUICTransportParams-Server-RejectMissing-" + ver.name,
-				config: Config{
-					MinVersion: ver.version,
-					MaxVersion: ver.version,
-				},
-				flags: []string{
-					"-quic-transport-params",
-					base64.StdEncoding.EncodeToString([]byte{3, 4}),
-				},
-				expectations: connectionExpectations{
-					quicTransportParams: []byte{3, 4},
-				},
-				shouldFail:                true,
-				expectedError:             ":MISSING_EXTENSION:",
-				skipTransportParamsConfig: true,
-			})
-		}
-		testCases = append(testCases, testCase{
-			testType: clientTest,
-			name:     "QUICTransportParams-Client-NotSentInTLS-" + ver.name,
-			config: Config{
-				MinVersion: ver.version,
-				MaxVersion: ver.version,
-			},
-			flags: []string{
-				"-max-version",
-				strconv.Itoa(int(ver.version)),
-				"-quic-transport-params",
-				base64.StdEncoding.EncodeToString([]byte{3, 4}),
-			},
-			shouldFail:                true,
-			expectedError:             ":QUIC_TRANSPORT_PARAMETERS_MISCONFIGURED:",
-			skipTransportParamsConfig: true,
-		})
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "QUICTransportParams-Server-RejectedInTLS-" + ver.name,
-			config: Config{
-				MinVersion:          ver.version,
-				MaxVersion:          ver.version,
-				QUICTransportParams: []byte{1, 2},
-			},
-			flags: []string{
-				"-expect-quic-transport-params",
-				base64.StdEncoding.EncodeToString([]byte{1, 2}),
-			},
-			shouldFail:                true,
-			expectedLocalError:        "remote error: unsupported extension",
-			skipTransportParamsConfig: true,
-		})
-
-		// Test ticket behavior.
-
-		// Resume with a corrupt ticket.
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "CorruptTicket-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				Bugs: ProtocolBugs{
-					FilterTicket: func(in []byte) ([]byte, error) {
-						in[len(in)-1] ^= 1
-						return in, nil
-					},
-				},
-			},
-			resumeSession:        true,
-			expectResumeRejected: true,
-		})
-		// Test the ticket callback, with and without renewal.
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "TicketCallback-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-			},
-			resumeSession: true,
-			flags:         []string{"-use-ticket-callback"},
-		})
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "TicketCallback-Renew-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				Bugs: ProtocolBugs{
-					ExpectNewTicket: true,
-				},
-			},
-			flags:         []string{"-use-ticket-callback", "-renew-ticket"},
-			resumeSession: true,
-		})
-
-		// Test that the ticket callback is only called once when everything before
-		// it in the ClientHello is asynchronous. This corrupts the ticket so
-		// certificate selection callbacks run.
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "TicketCallback-SingleCall-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				Bugs: ProtocolBugs{
-					FilterTicket: func(in []byte) ([]byte, error) {
-						in[len(in)-1] ^= 1
-						return in, nil
-					},
-				},
-			},
-			resumeSession:        true,
-			expectResumeRejected: true,
-			flags: []string{
-				"-use-ticket-callback",
-				"-async",
-			},
-		})
-
-		// Resume with various lengths of ticket session id.
-		if ver.version < VersionTLS13 {
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "TicketSessionIDLength-0-" + ver.name,
-				config: Config{
-					MaxVersion: ver.version,
-					Bugs: ProtocolBugs{
-						EmptyTicketSessionID: true,
-					},
-				},
-				resumeSession: true,
-			})
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "TicketSessionIDLength-16-" + ver.name,
-				config: Config{
-					MaxVersion: ver.version,
-					Bugs: ProtocolBugs{
-						TicketSessionIDLength: 16,
-					},
-				},
-				resumeSession: true,
-			})
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "TicketSessionIDLength-32-" + ver.name,
-				config: Config{
-					MaxVersion: ver.version,
-					Bugs: ProtocolBugs{
-						TicketSessionIDLength: 32,
-					},
-				},
-				resumeSession: true,
-			})
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "TicketSessionIDLength-33-" + ver.name,
-				config: Config{
-					MaxVersion: ver.version,
-					Bugs: ProtocolBugs{
-						TicketSessionIDLength: 33,
-					},
-				},
-				resumeSession: true,
-				shouldFail:    true,
-				// The maximum session ID length is 32.
-				expectedError: ":DECODE_ERROR:",
-			})
-		}
-
-		// Basic DTLS-SRTP tests. Include fake profiles to ensure they
-		// are ignored.
-		if ver.hasDTLS {
-			testCases = append(testCases, testCase{
-				protocol: dtls,
-				name:     "SRTP-Client-" + ver.name,
-				config: Config{
-					MaxVersion:             ver.version,
-					SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42},
-				},
-				flags: []string{
-					"-srtp-profiles",
-					"SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
-				},
-				expectations: connectionExpectations{
-					srtpProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80,
-				},
-			})
-			testCases = append(testCases, testCase{
-				protocol: dtls,
-				testType: serverTest,
-				name:     "SRTP-Server-" + ver.name,
-				config: Config{
-					MaxVersion:             ver.version,
-					SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42},
-				},
-				flags: []string{
-					"-srtp-profiles",
-					"SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
-				},
-				expectations: connectionExpectations{
-					srtpProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80,
-				},
-			})
-			// Test that the MKI is ignored.
-			testCases = append(testCases, testCase{
-				protocol: dtls,
-				testType: serverTest,
-				name:     "SRTP-Server-IgnoreMKI-" + ver.name,
-				config: Config{
-					MaxVersion:             ver.version,
-					SRTPProtectionProfiles: []uint16{SRTP_AES128_CM_HMAC_SHA1_80},
-					Bugs: ProtocolBugs{
-						SRTPMasterKeyIdentifer: "bogus",
-					},
-				},
-				flags: []string{
-					"-srtp-profiles",
-					"SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
-				},
-				expectations: connectionExpectations{
-					srtpProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80,
-				},
-			})
-			// Test that SRTP isn't negotiated on the server if there were
-			// no matching profiles.
-			testCases = append(testCases, testCase{
-				protocol: dtls,
-				testType: serverTest,
-				name:     "SRTP-Server-NoMatch-" + ver.name,
-				config: Config{
-					MaxVersion:             ver.version,
-					SRTPProtectionProfiles: []uint16{100, 101, 102},
-				},
-				flags: []string{
-					"-srtp-profiles",
-					"SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32",
-				},
-				expectations: connectionExpectations{
-					srtpProtectionProfile: 0,
-				},
-			})
-			// Test that the server returning an invalid SRTP profile is
-			// flagged as an error by the client.
-			testCases = append(testCases, testCase{
-				protocol: dtls,
-				name:     "SRTP-Client-NoMatch-" + ver.name,
-				config: Config{
-					MaxVersion: ver.version,
-					Bugs: ProtocolBugs{
-						SendSRTPProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_32,
-					},
-				},
-				flags: []string{
-					"-srtp-profiles",
-					"SRTP_AES128_CM_SHA1_80",
-				},
-				shouldFail:    true,
-				expectedError: ":BAD_SRTP_PROTECTION_PROFILE_LIST:",
-			})
-		}
-
-		// Test SCT list.
-		testCases = append(testCases, testCase{
-			name:     "SignedCertificateTimestampList-Client-" + ver.name,
-			testType: clientTest,
-			config: Config{
-				MaxVersion: ver.version,
-			},
-			flags: []string{
-				"-enable-signed-cert-timestamps",
-				"-expect-signed-cert-timestamps",
-				base64.StdEncoding.EncodeToString(testSCTList),
-			},
-			resumeSession: true,
-		})
-
-		var differentSCTList []byte
-		differentSCTList = append(differentSCTList, testSCTList...)
-		differentSCTList[len(differentSCTList)-1] ^= 1
-
-		// The SCT extension did not specify that it must only be sent on resumption as it
-		// should have, so test that we tolerate but ignore it.
-		testCases = append(testCases, testCase{
-			name: "SendSCTListOnResume-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				Bugs: ProtocolBugs{
-					SendSCTListOnResume: differentSCTList,
-				},
-			},
-			flags: []string{
-				"-enable-signed-cert-timestamps",
-				"-expect-signed-cert-timestamps",
-				base64.StdEncoding.EncodeToString(testSCTList),
-			},
-			resumeSession: true,
-		})
-
-		testCases = append(testCases, testCase{
-			name:     "SignedCertificateTimestampList-Server-" + ver.name,
-			testType: serverTest,
-			config: Config{
-				MaxVersion: ver.version,
-			},
-			flags: []string{
-				"-signed-cert-timestamps",
-				base64.StdEncoding.EncodeToString(testSCTList),
-			},
-			expectations: connectionExpectations{
-				sctList: testSCTList,
-			},
-			resumeSession: true,
-		})
-
-		emptySCTListCert := *testCerts[0].cert
-		emptySCTListCert.SignedCertificateTimestampList = []byte{0, 0}
-
-		// Test empty SCT list.
-		testCases = append(testCases, testCase{
-			name:     "SignedCertificateTimestampListEmpty-Client-" + ver.name,
-			testType: clientTest,
-			config: Config{
-				MaxVersion:   ver.version,
-				Certificates: []Certificate{emptySCTListCert},
-			},
-			flags: []string{
-				"-enable-signed-cert-timestamps",
-			},
-			shouldFail:    true,
-			expectedError: ":ERROR_PARSING_EXTENSION:",
-		})
-
-		emptySCTCert := *testCerts[0].cert
-		emptySCTCert.SignedCertificateTimestampList = []byte{0, 6, 0, 2, 1, 2, 0, 0}
-
-		// Test empty SCT in non-empty list.
-		testCases = append(testCases, testCase{
-			name:     "SignedCertificateTimestampListEmptySCT-Client-" + ver.name,
-			testType: clientTest,
-			config: Config{
-				MaxVersion:   ver.version,
-				Certificates: []Certificate{emptySCTCert},
-			},
-			flags: []string{
-				"-enable-signed-cert-timestamps",
-			},
-			shouldFail:    true,
-			expectedError: ":ERROR_PARSING_EXTENSION:",
-		})
-
-		// Test that certificate-related extensions are not sent unsolicited.
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			name:     "UnsolicitedCertificateExtensions-" + ver.name,
-			config: Config{
-				MaxVersion: ver.version,
-				Bugs: ProtocolBugs{
-					NoOCSPStapling:                true,
-					NoSignedCertificateTimestamps: true,
-				},
-			},
-			flags: []string{
-				"-ocsp-response",
-				base64.StdEncoding.EncodeToString(testOCSPResponse),
-				"-signed-cert-timestamps",
-				base64.StdEncoding.EncodeToString(testSCTList),
-			},
-		})
 	}
 
 	testCases = append(testCases, testCase{
diff --git a/util/testresult/testresult.go b/util/testresult/testresult.go
index 5226716..b353ece 100644
--- a/util/testresult/testresult.go
+++ b/util/testresult/testresult.go
@@ -18,6 +18,7 @@
 
 import (
 	"encoding/json"
+	"fmt"
 	"os"
 	"time"
 )
@@ -44,7 +45,7 @@
 
 func (t *Results) addResult(name, result, expected string) {
 	if _, found := t.Tests[name]; found {
-		panic(name)
+		panic(fmt.Sprintf("duplicate test name %q", name))
 	}
 	t.Tests[name] = Result{
 		Actual:       result,