Add initial DTLS tests.

Change-Id: I7407261bdb2d788c879f2e67e617a615d9ff8f8b
Reviewed-on: https://boringssl-review.googlesource.com/1505
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/runner/packet_adapter.go b/ssl/test/runner/packet_adapter.go
new file mode 100644
index 0000000..b2f2765
--- /dev/null
+++ b/ssl/test/runner/packet_adapter.go
@@ -0,0 +1,50 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"encoding/binary"
+	"errors"
+	"net"
+)
+
+type packetAdaptor struct {
+	net.Conn
+}
+
+func newPacketAdaptor(conn net.Conn) net.Conn {
+	return &packetAdaptor{conn}
+}
+
+func (p *packetAdaptor) Read(b []byte) (int, error) {
+	var length uint32
+	if err := binary.Read(p.Conn, binary.BigEndian, &length); err != nil {
+		return 0, err
+	}
+	out := make([]byte, length)
+	n, err := p.Conn.Read(out)
+	if err != nil {
+		return 0, err
+	}
+	if n != int(length) {
+		return 0, errors.New("internal error: length mismatch!")
+	}
+	return copy(b, out), nil
+}
+
+func (p *packetAdaptor) Write(b []byte) (int, error) {
+	length := uint32(len(b))
+	if err := binary.Write(p.Conn, binary.BigEndian, length); err != nil {
+		return 0, err
+	}
+	n, err := p.Conn.Write(b)
+	if err != nil {
+		return 0, err
+	}
+	if n != len(b) {
+		return 0, errors.New("internal error: length mismatch!")
+	}
+	return len(b), nil
+}
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 4fd5a1d..1d44f99 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -63,8 +63,16 @@
 	serverTest
 )
 
+type protocol int
+
+const (
+	tls protocol = iota
+	dtls
+)
+
 type testCase struct {
 	testType      testType
+	protocol      protocol
 	name          string
 	config        Config
 	shouldFail    bool
@@ -341,18 +349,6 @@
 		expectedError: ":CCS_RECEIVED_EARLY:",
 	},
 	{
-		name: "FalseStart-SessionTicketsDisabled",
-		config: Config{
-			CipherSuites:           []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
-			NextProtos:             []string{"foo"},
-			SessionTicketsDisabled: true,
-		},
-		flags: []string{
-			"-false-start",
-			"-select-next-proto", "foo",
-		},
-	},
-	{
 		testType: serverTest,
 		name:     "FallbackSCSV",
 		config: Config{
@@ -454,16 +450,30 @@
 }
 
 func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int) error {
-	if _, err := conn.Write([]byte(test.sendPrefix)); err != nil {
-		return err
+	if test.protocol == dtls {
+		conn = newPacketAdaptor(conn)
+	}
+
+	if test.sendPrefix != "" {
+		if _, err := conn.Write([]byte(test.sendPrefix)); err != nil {
+			return err
+		}
 	}
 
 	var tlsConn *Conn
 	if test.testType == clientTest {
-		tlsConn = Server(conn, config)
+		if test.protocol == dtls {
+			tlsConn = DTLSServer(conn, config)
+		} else {
+			tlsConn = Server(conn, config)
+		}
 	} else {
 		config.InsecureSkipVerify = true
-		tlsConn = Client(conn, config)
+		if test.protocol == dtls {
+			tlsConn = DTLSClient(conn, config)
+		} else {
+			tlsConn = Client(conn, config)
+		}
 	}
 
 	if err := tlsConn.Handshake(); err != nil {
@@ -475,6 +485,9 @@
 	}
 
 	if messageLen < 0 {
+		if test.protocol == dtls {
+			return fmt.Errorf("messageLen < 0 not supported for DTLS tests")
+		}
 		// Read until EOF.
 		_, err := io.Copy(ioutil.Discard, tlsConn)
 		return err
@@ -490,9 +503,21 @@
 	tlsConn.Write(testMessage)
 
 	buf := make([]byte, len(testMessage))
-	_, err := io.ReadFull(tlsConn, buf)
-	if err != nil {
-		return err
+	if test.protocol == dtls {
+		bufTmp := make([]byte, len(buf)+1)
+		n, err := tlsConn.Read(bufTmp)
+		if err != nil {
+			return err
+		}
+		if n != len(buf) {
+			return fmt.Errorf("bad reply; length mismatch (%d vs %d)", n, len(buf))
+		}
+		copy(buf, bufTmp)
+	} else {
+		_, err := io.ReadFull(tlsConn, buf)
+		if err != nil {
+			return err
+		}
 	}
 
 	for i, v := range buf {
@@ -568,6 +593,10 @@
 		}
 	}
 
+	if test.protocol == dtls {
+		flags = append(flags, "-dtls")
+	}
+
 	if test.resumeSession {
 		flags = append(flags, "-resume")
 	}
@@ -746,6 +775,36 @@
 					resumeSession: resumeSession,
 				})
 			}
+
+			// TODO(davidben): Fix DTLS 1.2 support and test that.
+			if ver.version == VersionTLS10 && strings.Index(suite.name, "RC4") == -1 {
+				testCases = append(testCases, testCase{
+					testType: clientTest,
+					protocol: dtls,
+					name:     "D" + ver.name + "-" + suite.name + "-client",
+					config: Config{
+						MinVersion:   ver.version,
+						MaxVersion:   ver.version,
+						CipherSuites: []uint16{suite.id},
+						Certificates: []Certificate{cert},
+					},
+					resumeSession: resumeSession,
+				})
+				testCases = append(testCases, testCase{
+					testType: serverTest,
+					protocol: dtls,
+					name:     "D" + ver.name + "-" + suite.name + "-server",
+					config: Config{
+						MinVersion:   ver.version,
+						MaxVersion:   ver.version,
+						CipherSuites: []uint16{suite.id},
+						Certificates: []Certificate{cert},
+					},
+					certFile:      certFile,
+					keyFile:       keyFile,
+					resumeSession: resumeSession,
+				})
+			}
 		}
 	}
 }
@@ -918,15 +977,18 @@
 // 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) {
+func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) {
 	var suffix string
 	var flags []string
 	var maxHandshakeRecordLength int
+	if protocol == dtls {
+		suffix = "-DTLS"
+	}
 	if async {
-		suffix = "-Async"
+		suffix += "-Async"
 		flags = append(flags, "-async")
 	} else {
-		suffix = "-Sync"
+		suffix += "-Sync"
 	}
 	if splitHandshake {
 		suffix += "-SplitHandshakeRecords"
@@ -935,7 +997,8 @@
 
 	// Basic handshake, with resumption. Client and server.
 	testCases = append(testCases, testCase{
-		name: "Basic-Client" + suffix,
+		protocol: protocol,
+		name:     "Basic-Client" + suffix,
 		config: Config{
 			Bugs: ProtocolBugs{
 				MaxHandshakeRecordLength: maxHandshakeRecordLength,
@@ -945,7 +1008,8 @@
 		resumeSession: true,
 	})
 	testCases = append(testCases, testCase{
-		name: "Basic-Client-RenewTicket" + suffix,
+		protocol: protocol,
+		name:     "Basic-Client-RenewTicket" + suffix,
 		config: Config{
 			Bugs: ProtocolBugs{
 				MaxHandshakeRecordLength: maxHandshakeRecordLength,
@@ -956,6 +1020,7 @@
 		resumeSession: true,
 	})
 	testCases = append(testCases, testCase{
+		protocol: protocol,
 		testType: serverTest,
 		name:     "Basic-Server" + suffix,
 		config: Config{
@@ -967,9 +1032,36 @@
 		resumeSession: true,
 	})
 
+	// TLS client auth.
+	testCases = append(testCases, testCase{
+		protocol: protocol,
+		testType: clientTest,
+		name:     "ClientAuth-Client" + suffix,
+		config: Config{
+			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
+			ClientAuth:   RequireAnyClientCert,
+			Bugs: ProtocolBugs{
+				MaxHandshakeRecordLength: maxHandshakeRecordLength,
+			},
+		},
+		flags: append(flags,
+			"-cert-file", rsaCertificateFile,
+			"-key-file", rsaKeyFile),
+	})
+	testCases = append(testCases, testCase{
+		protocol: protocol,
+		testType: serverTest,
+		name:     "ClientAuth-Server" + suffix,
+		config: Config{
+			Certificates: []Certificate{rsaCertificate},
+		},
+		flags: append(flags, "-require-any-client-certificate"),
+	})
+
 	// No session ticket support; server doesn't send NewSessionTicket.
 	testCases = append(testCases, testCase{
-		name: "SessionTicketsDisabled-Client" + suffix,
+		protocol: protocol,
+		name:     "SessionTicketsDisabled-Client" + suffix,
 		config: Config{
 			SessionTicketsDisabled: true,
 			Bugs: ProtocolBugs{
@@ -979,6 +1071,7 @@
 		flags: flags,
 	})
 	testCases = append(testCases, testCase{
+		protocol: protocol,
 		testType: serverTest,
 		name:     "SessionTicketsDisabled-Server" + suffix,
 		config: Config{
@@ -990,88 +1083,108 @@
 		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,
+	if protocol == tls {
+		// NPN on client and server; results in post-handshake message.
+		testCases = append(testCases, testCase{
+			protocol: protocol,
+			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, "-select-next-proto", "foo"),
+		})
+		testCases = append(testCases, testCase{
+			protocol: protocol,
+			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"),
-	})
+			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,
+		// Client does False Start and negotiates NPN.
+		testCases = append(testCases, testCase{
+			protocol: protocol,
+			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,
-	})
+			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,
+		// False Start without session tickets.
+		testCases = append(testCases, testCase{
+			name: "FalseStart-SessionTicketsDisabled",
+			config: Config{
+				CipherSuites:           []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+				NextProtos:             []string{"foo"},
+				SessionTicketsDisabled: true,
 			},
-		},
-		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"),
-	})
+			flags: []string{
+				"-false-start",
+				"-select-next-proto", "foo",
+			},
+		})
 
-	// Client sends a V2ClientHello.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SendV2ClientHello" + suffix,
-		config: Config{
-			// Choose a cipher suite that does not involve
-			// elliptic curves, so no extensions are
-			// involved.
-			CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
-			Bugs: ProtocolBugs{
-				MaxHandshakeRecordLength: maxHandshakeRecordLength,
-				SendV2ClientHello:        true,
+		// Client sends a V2ClientHello.
+		testCases = append(testCases, testCase{
+			protocol: protocol,
+			testType: serverTest,
+			name:     "SendV2ClientHello" + suffix,
+			config: Config{
+				// Choose a cipher suite that does not involve
+				// elliptic curves, so no extensions are
+				// involved.
+				CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
+				Bugs: ProtocolBugs{
+					MaxHandshakeRecordLength: maxHandshakeRecordLength,
+					SendV2ClientHello:        true,
+				},
 			},
-		},
-		flags: flags,
-	})
+			flags: flags,
+		})
+	} else {
+		testCases = append(testCases, testCase{
+			protocol: protocol,
+			name:     "SkipHelloVerifyRequest" + suffix,
+			config: Config{
+				Bugs: ProtocolBugs{
+					MaxHandshakeRecordLength: maxHandshakeRecordLength,
+					SkipHelloVerifyRequest:   true,
+				},
+			},
+			flags: flags,
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			protocol: protocol,
+			name:     "CookieExchange" + suffix,
+			config: Config{
+				Bugs: ProtocolBugs{
+					MaxHandshakeRecordLength: maxHandshakeRecordLength,
+				},
+			},
+			flags: append(flags, "-cookie-exchange"),
+		})
+	}
 }
 
 func addVersionNegotiationTests() {
@@ -1169,7 +1282,9 @@
 	addVersionNegotiationTests()
 	for _, async := range []bool{false, true} {
 		for _, splitHandshake := range []bool{false, true} {
-			addStateMachineCoverageTests(async, splitHandshake)
+			for _, protocol := range []protocol{tls, dtls} {
+				addStateMachineCoverageTests(async, splitHandshake, protocol)
+			}
 		}
 	}