Test state machine asynchronous behavior.

Add a framework for testing the asynchronous codepath. Move some handshake
state machine coverage tests to cover a range of record-layer and
handshake-layer asynchronicity.

This adds tests for the previous two async bugs fixed.

Change-Id: I422ef33ba3eeb0ad04766871ed8bc59b677b169e
Reviewed-on: https://boringssl-review.googlesource.com/1410
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 4f34ce4..eb1d57c 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -391,6 +391,11 @@
 	// SendFallbackSCSV causes the client to include
 	// TLS_FALLBACK_SCSV in the ClientHello.
 	SendFallbackSCSV bool
+
+	// MaxHandshakeRecordLength, if non-zero, is the maximum size of a
+	// handshake record. Handshake messages will be split at the record
+	// layer.
+	MaxHandshakeRecordLength int
 }
 
 func (c *Config) serverInit() {
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 02ed8f0..52582ad 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -719,6 +719,9 @@
 		if m > maxPlaintext {
 			m = maxPlaintext
 		}
+		if typ == recordTypeHandshake && c.config.Bugs.MaxHandshakeRecordLength > 0 && m > c.config.Bugs.MaxHandshakeRecordLength {
+			m = c.config.Bugs.MaxHandshakeRecordLength
+		}
 		explicitIVLen := 0
 		explicitIVIsSeq := false
 
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index cf9e44c..bdd3566 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -218,17 +218,6 @@
 		expectedError: ":UNEXPECTED_MESSAGE:",
 	},
 	{
-		testType: serverTest,
-		name:     "NPNServerTest",
-		config: Config{
-			NextProtos: []string{"bar"},
-		},
-		flags: []string{
-			"-advertise-npn", "\x03foo\x03bar\x03baz",
-			"-expect-next-proto", "bar",
-		},
-	},
-	{
 		name: "SkipChangeCipherSpec-Client",
 		config: Config{
 			Bugs: ProtocolBugs{
@@ -323,19 +312,6 @@
 		expectedError: ":CCS_RECEIVED_EARLY:",
 	},
 	{
-		name: "SessionTicketsDisabled-Client",
-		config: Config{
-			SessionTicketsDisabled: true,
-		},
-	},
-	{
-		testType: serverTest,
-		name:     "SessionTicketsDisabled-Server",
-		config: Config{
-			SessionTicketsDisabled: true,
-		},
-	},
-	{
 		name: "SkipNewSessionTicket",
 		config: Config{
 			Bugs: ProtocolBugs{
@@ -346,18 +322,6 @@
 		expectedError: ":CCS_RECEIVED_EARLY:",
 	},
 	{
-		name: "FalseStart",
-		config: Config{
-			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
-			NextProtos:   []string{"foo"},
-		},
-		flags: []string{
-			"-false-start",
-			"-select-next-proto", "foo",
-		},
-		resumeSession: true,
-	},
-	{
 		name: "FalseStart-SessionTicketsDisabled",
 		config: Config{
 			CipherSuites:           []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
@@ -824,6 +788,135 @@
 	}
 }
 
+// Adds tests that try to cover the range of the handshake state machine, under
+// various conditions. Some of these are redundant with other tests, but they
+// only cover the synchronous case.
+func addStateMachineCoverageTests(async bool, splitHandshake bool) {
+	var suffix string
+	var flags []string
+	var maxHandshakeRecordLength int
+	if async {
+		suffix = "-Async"
+		flags = append(flags, "-async")
+	} else {
+		suffix = "-Sync"
+	}
+	if splitHandshake {
+		suffix += "-SplitHandshakeRecords"
+		maxHandshakeRecordLength = 10
+	}
+
+	// Basic handshake, with resumption. Client and server.
+	testCases = append(testCases, testCase{
+		name: "Basic-Client" + suffix,
+		config: Config{
+			Bugs: ProtocolBugs{
+				MaxHandshakeRecordLength: maxHandshakeRecordLength,
+			},
+		},
+		flags: flags,
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "Basic-Server" + suffix,
+		config: Config{
+			Bugs: ProtocolBugs{
+				MaxHandshakeRecordLength: maxHandshakeRecordLength,
+			},
+		},
+		flags: flags,
+	})
+
+	// No session ticket support; server doesn't send NewSessionTicket.
+	testCases = append(testCases, testCase{
+		name: "SessionTicketsDisabled-Client" + suffix,
+		config: Config{
+			SessionTicketsDisabled: true,
+			Bugs: ProtocolBugs{
+				MaxHandshakeRecordLength: maxHandshakeRecordLength,
+			},
+		},
+		flags: flags,
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "SessionTicketsDisabled-Server" + suffix,
+		config: Config{
+			SessionTicketsDisabled: true,
+			Bugs: ProtocolBugs{
+				MaxHandshakeRecordLength: maxHandshakeRecordLength,
+			},
+		},
+		flags: flags,
+	})
+
+	// NPN on client and server; results in post-handshake message.
+	testCases = append(testCases, testCase{
+		name: "NPN-Client" + suffix,
+		config: Config{
+			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+			NextProtos:   []string{"foo"},
+			Bugs: ProtocolBugs{
+				MaxHandshakeRecordLength: maxHandshakeRecordLength,
+			},
+		},
+		flags: append(flags, "-select-next-proto", "foo"),
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "NPN-Server" + suffix,
+		config: Config{
+			NextProtos: []string{"bar"},
+			Bugs: ProtocolBugs{
+				MaxHandshakeRecordLength: maxHandshakeRecordLength,
+			},
+		},
+		flags: append(flags,
+			"-advertise-npn", "\x03foo\x03bar\x03baz",
+			"-expect-next-proto", "bar"),
+	})
+
+	// Client does False Start and negotiates NPN.
+	testCases = append(testCases, testCase{
+		name: "FalseStart" + suffix,
+		config: Config{
+			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+			NextProtos:   []string{"foo"},
+			Bugs: ProtocolBugs{
+				MaxHandshakeRecordLength: maxHandshakeRecordLength,
+			},
+		},
+		flags: append(flags,
+			"-false-start",
+			"-select-next-proto", "foo"),
+		resumeSession: true,
+	})
+
+	// TLS client auth.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "ClientAuth-Client" + suffix,
+		config: Config{
+			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+			ClientAuth:   RequireAnyClientCert,
+			Bugs: ProtocolBugs{
+				MaxHandshakeRecordLength: maxHandshakeRecordLength,
+			},
+		},
+		flags: append(flags,
+			"-cert-file", rsaCertificateFile,
+			"-key-file", rsaKeyFile),
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "ClientAuth-Server" + suffix,
+		config: Config{
+			Certificates: []Certificate{rsaCertificate},
+		},
+		flags: append(flags, "-require-any-client-certificate"),
+	})
+}
+
 func worker(statusChan chan statusMsg, c chan *testCase, buildDir string, wg *sync.WaitGroup) {
 	defer wg.Done()
 
@@ -874,6 +967,11 @@
 	addBadECDSASignatureTests()
 	addCBCPaddingTests()
 	addClientAuthTests()
+	for _, async := range []bool{false, true} {
+		for _, splitHandshake := range []bool{false, true} {
+			addStateMachineCoverageTests(async, splitHandshake)
+		}
+	}
 
 	var wg sync.WaitGroup