Adding benchmarks for socket implementations. (#75)

diff --git a/build.gradle b/build.gradle
index d6e84ab..3ee0373 100644
--- a/build.gradle
+++ b/build.gradle
@@ -77,8 +77,8 @@
                 bouncycastle_apis: 'org.bouncycastle:bcpkix-jdk15on:1.55',
 
                 // Benchmark dependencies
-                jmh_generator_annprocess : "org.openjdk.jmh:jmh-generator-annprocess:$jmhVersion",
-                netty: 'io.netty:netty-codec-http2:4.1.8.Final',
+                jmh_core: "org.openjdk.jmh:jmh-core:${jmhVersion}",
+                netty_handler: 'io.netty:netty-handler:4.1.8.Final',
                 netty_tcnative: 'io.netty:netty-tcnative-boringssl-static:1.1.33.Fork26',
         ]
     }
diff --git a/common/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java b/common/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java
index ca5b11d..c2272d5 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java
@@ -16,8 +16,6 @@
 
 package org.conscrypt;
 
-import static org.conscrypt.Platform.getFileDescriptor;
-
 import java.io.IOException;
 import java.net.InetAddress;
 import java.net.Socket;
@@ -25,11 +23,12 @@
 import java.security.KeyManagementException;
 
 public class OpenSSLSocketFactoryImpl extends javax.net.ssl.SSLSocketFactory {
-    private static boolean alwaysUseSocketEngine =
-            Boolean.parseBoolean(System.getProperty("org.conscrypt.alwaysUseEngineSocket"));
+    private static boolean useEngineSocketByDefault =
+            Boolean.parseBoolean(System.getProperty("org.conscrypt.useEngineSocketByDefault"));
 
     private final SSLParametersImpl sslParameters;
     private final IOException instantiationException;
+    private boolean useEngineSocket = useEngineSocketByDefault;
 
     public OpenSSLSocketFactoryImpl() {
         SSLParametersImpl sslParametersLocal = null;
@@ -49,8 +48,19 @@
         this.instantiationException = null;
     }
 
-    public static void setAlwaysUseEngineSocket(boolean enable) {
-        alwaysUseSocketEngine = enable;
+    /**
+     * Configures the default socket to be created for all instances.
+     */
+    public static void setUseEngineSocketByDefault(boolean useEngineSocket) {
+        useEngineSocketByDefault = useEngineSocket;
+    }
+
+    /**
+     * Configures the socket to be created for this instance. If not called,
+     * {@link #useEngineSocketByDefault} will be used.
+     */
+    public void setUseEngineSocket(boolean useEngineSocket) {
+        this.useEngineSocket = useEngineSocket;
     }
 
     @Override
@@ -115,18 +125,12 @@
         } catch (RuntimeException re) {
             // Ignore
         }
-        if (socketHasFd && !alwaysUseSocketEngine) {
-            return new OpenSSLSocketImplWrapper(s,
-                hostname,
-                port,
-                autoClose,
-                (SSLParametersImpl) sslParameters.clone());
+        if (socketHasFd && !useEngineSocket) {
+            return new OpenSSLSocketImplWrapper(
+                    s, hostname, port, autoClose, (SSLParametersImpl) sslParameters.clone());
         } else {
-            return new OpenSSLEngineSocketImpl(s,
-                hostname,
-                port,
-                autoClose,
-                (SSLParametersImpl) sslParameters.clone());
+            return new OpenSSLEngineSocketImpl(
+                    s, hostname, port, autoClose, (SSLParametersImpl) sslParameters.clone());
         }
     }
 }
diff --git a/openjdk-benchmarks/build.gradle b/openjdk-benchmarks/build.gradle
index 2c4e1d1..7d82caa 100644
--- a/openjdk-benchmarks/build.gradle
+++ b/openjdk-benchmarks/build.gradle
@@ -22,14 +22,13 @@
 }
 
 dependencies {
-    compile project(path: ':conscrypt-openjdk', configuration: "$openJdkConfiguration"), libraries.guava
-    jmh libraries.junit,
-            libraries.netty,
-            libraries.netty_tcnative,
-            // JMH plugin doesn't seem to include this dependency by default. This version of the JMH
-            // plugin seems to require the use of v1.12 for all other JMH dependencies, so not overriding
-            // them here.
-            libraries.jmh_generator_annprocess
+    compile project(path: ':conscrypt-openjdk', configuration: "$openJdkConfiguration"),
+            libraries.guava,
+            libraries.junit,
+            libraries.netty_handler,
+            libraries.netty_tcnative
+
+    jmh libraries.jmh_core
 }
 
 // Running benchmarks in IntelliJ seems broken without this.
diff --git a/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/ClientSocketBenchmark.java b/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/ClientSocketBenchmark.java
new file mode 100644
index 0000000..ee7aee6
--- /dev/null
+++ b/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/ClientSocketBenchmark.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.conscrypt.benchmarks;
+
+import static org.conscrypt.benchmarks.Util.LOCALHOST;
+import static org.conscrypt.benchmarks.Util.getConscryptSocketFactory;
+import static org.conscrypt.benchmarks.Util.getJdkSocketFactory;
+import static org.conscrypt.benchmarks.Util.getProtocols;
+import static org.conscrypt.benchmarks.Util.newTextMessage;
+import static org.conscrypt.benchmarks.Util.pickUnusedPort;
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+/**
+ * Benchmark for comparing performance of client socket implementations. All benchmarks use Netty
+ * with tcnative as the server.
+ */
+@State(Scope.Benchmark)
+public class ClientSocketBenchmark {
+    /**
+     * Various factories for SSL sockets.
+     */
+    public enum SslSocketType {
+        JDK {
+            private final SSLSocketFactory socketFactory = getJdkSocketFactory();
+            @Override
+            SSLSocket newSslSocket(String host, int port) {
+                try {
+                    return (SSLSocket) socketFactory.createSocket(host, port);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        },
+        CONSCRYPT {
+            private final SSLSocketFactory socketFactory = getConscryptSocketFactory(false);
+            @Override
+            SSLSocket newSslSocket(String host, int port) {
+                try {
+                    return (SSLSocket) socketFactory.createSocket(host, port);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        },
+        CONSCRYPT_ENGINE {
+            private final SSLSocketFactory socketFactory = getConscryptSocketFactory(true);
+            @Override
+            SSLSocket newSslSocket(String host, int port) {
+                try {
+                    return (SSLSocket) socketFactory.createSocket(
+                            SocketFactory.getDefault().createSocket(host, port), host, port, true);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        };
+
+        final SSLSocket newSslSocket(String host, int port, String cipher) {
+            try {
+                SSLSocket sslSocket = newSslSocket(host, port);
+                sslSocket.setEnabledProtocols(getProtocols());
+                sslSocket.setEnabledCipherSuites(new String[] {cipher});
+                return sslSocket;
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        abstract SSLSocket newSslSocket(String host, int port);
+    }
+
+    @Param public SslSocketType sslSocketType;
+
+    @Param({"64", "128", "512", "1024", "4096"}) public int messageSize;
+
+    @Param({"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"}) public String cipher;
+
+    private TestClient client;
+    private NettyEchoServer server;
+    private byte[] message;
+    private byte[] response;
+
+    @Setup
+    public void setup() throws Exception {
+        message = newTextMessage(messageSize);
+        response = new byte[messageSize];
+
+        int port = pickUnusedPort();
+        server = new NettyEchoServer(port, messageSize, cipher);
+        server.start();
+
+        client = new TestClient(sslSocketType.newSslSocket(LOCALHOST, port, cipher));
+        client.start();
+    }
+
+    @TearDown
+    public void teardown() throws Exception {
+        client.stop();
+        server.stop();
+    }
+
+    @Benchmark
+    public void pingPong() throws IOException {
+        client.sendMessage(message);
+        int numBytes = client.readMessage(response);
+        assertEquals(messageSize, numBytes);
+    }
+
+    public static void main(String[] args) throws Exception {
+        ClientSocketBenchmark bm = new ClientSocketBenchmark();
+        bm.sslSocketType = SslSocketType.CONSCRYPT_ENGINE;
+        bm.messageSize = 1024;
+        bm.cipher = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256";
+        bm.setup();
+        try {
+            while (true) {
+                if (Thread.interrupted()) {
+                    break;
+                }
+                bm.pingPong();
+            }
+        } finally {
+            bm.teardown();
+        }
+    }
+}
diff --git a/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/ServerSocketBenchmark.java b/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/ServerSocketBenchmark.java
new file mode 100644
index 0000000..c00a9c5
--- /dev/null
+++ b/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/ServerSocketBenchmark.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.conscrypt.benchmarks;
+
+import static org.conscrypt.benchmarks.Util.getProtocols;
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+/**
+ * Benchmark for comparing performance of server socket implementations. All benchmarks use the
+ * standard JDK TLS implementation.
+ */
+@State(Scope.Benchmark)
+public class ServerSocketBenchmark {
+    /**
+     * Various factories for SSL server sockets.
+     */
+    public enum SslSocketType {
+        JDK(Util.getJdkServerSocketFactory()),
+        CONSCRYPT(Util.getConscryptServerSocketFactory());
+
+        private final SSLServerSocketFactory serverSocketFactory;
+
+        SslSocketType(SSLServerSocketFactory serverSocketFactory) {
+            this.serverSocketFactory = serverSocketFactory;
+        }
+
+        final SSLServerSocket newServerSocket(String cipher) {
+            try {
+                int port = Util.pickUnusedPort();
+                SSLServerSocket sslSocket =
+                        (SSLServerSocket) serverSocketFactory.createServerSocket(port);
+                sslSocket.setEnabledProtocols(Util.getProtocols());
+                sslSocket.setEnabledCipherSuites(new String[] {cipher});
+                return sslSocket;
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    @Param public SslSocketType sslSocketType;
+
+    @Param({"64", "128", "512", "1024", "4096"}) public int messageSize;
+
+    @Param({"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"}) public String cipher;
+
+    private TestClient client;
+    private EchoServer server;
+    private byte[] message;
+    private byte[] response;
+
+    @Setup
+    public void setup() throws Exception {
+        message = Util.newTextMessage(messageSize);
+        response = new byte[messageSize];
+
+        server = new EchoServer(sslSocketType.newServerSocket(cipher), messageSize);
+
+        Future connectedFuture = server.start();
+
+        SSLSocket socket;
+        try {
+            SSLSocketFactory socketFactory = Util.getJdkSocketFactory();
+            socket = (SSLSocket) socketFactory.createSocket(Util.LOCALHOST, server.port());
+            socket.setEnabledProtocols(getProtocols());
+            socket.setEnabledCipherSuites(new String[] {cipher});
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        client = new TestClient(socket);
+        client.start();
+
+        // Wait for the initial connection to complete.
+        connectedFuture.get(5, TimeUnit.SECONDS);
+    }
+
+    @TearDown
+    public void teardown() throws Exception {
+        client.stop();
+        server.stop();
+    }
+
+    @Benchmark
+    public void pingPong() throws IOException {
+        client.sendMessage(message);
+        int numBytes = client.readMessage(response);
+        assertEquals(messageSize, numBytes);
+    }
+}
diff --git a/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/SslEngineBenchmark.java b/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/SslEngineBenchmark.java
index 89cf457..cd0083e 100644
--- a/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/SslEngineBenchmark.java
+++ b/openjdk-benchmarks/src/jmh/java/org/conscrypt/benchmarks/SslEngineBenchmark.java
@@ -14,16 +14,37 @@
  * limitations under the License.
  */
 
+/*
+ * Copyright 2017 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
 package org.conscrypt.benchmarks;
 
 import static java.lang.Math.max;
+import static org.conscrypt.benchmarks.Util.PROTOCOL_TLS_V1_2;
+import static org.conscrypt.benchmarks.Util.initClientSslContext;
+import static org.conscrypt.benchmarks.Util.initEngine;
+import static org.conscrypt.benchmarks.Util.initServerContext;
+import static org.conscrypt.benchmarks.Util.newNettyClientContext;
+import static org.conscrypt.benchmarks.Util.newNettyServerContext;
+import static org.conscrypt.benchmarks.Util.newTextMessage;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
 import io.netty.buffer.UnpooledByteBufAllocator;
 import io.netty.handler.ssl.SslContext;
-import io.netty.handler.ssl.SslContextBuilder;
-import java.io.File;
 import java.nio.ByteBuffer;
 import java.security.NoSuchAlgorithmException;
 import javax.net.ssl.SSLContext;
@@ -42,14 +63,10 @@
  */
 @State(Scope.Benchmark)
 public class SslEngineBenchmark {
-    private static final byte[] CHARS =
-            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".getBytes();
-    private static final String PROTOCOL_TLS_V1_2 = "TLSv1.2";
-
     public enum SslProvider {
         JDK {
-            private final SSLContext clientContext = initContext(newContext(), true);
-            private final SSLContext serverContext = initContext(newContext(), false);
+            private final SSLContext clientContext = initClientSslContext(newContext());
+            private final SSLContext serverContext = initServerContext(newContext());
 
             @Override
             SSLEngine newClientEngine(String cipher) {
@@ -70,8 +87,8 @@
             }
         },
         CONSCRYPT {
-            private final SSLContext clientContext = initContext(newContext(), true);
-            private final SSLContext serverContext = initContext(newContext(), false);
+            private final SSLContext clientContext = initClientSslContext(newContext());
+            private final SSLContext serverContext = initServerContext(newContext());
 
             @Override
             SSLEngine newClientEngine(String cipher) {
@@ -92,8 +109,8 @@
             }
         },
         NETTY {
-            private final SslContext clientContext = newClientContext();
-            private final SslContext serverContext = newServerContext();
+            private final SslContext clientContext = newNettyClientContext(null);
+            private final SslContext serverContext = newNettyServerContext(null);
 
             @Override
             SSLEngine newClientEngine(String cipher) {
@@ -106,53 +123,10 @@
                 return initEngine(
                         serverContext.newEngine(UnpooledByteBufAllocator.DEFAULT), cipher, false);
             }
-
-            private SslContext newClientContext() {
-                try {
-                    File clientCert = Util.loadCert("ca.pem");
-                    return SslContextBuilder.forClient()
-                            .sslProvider(io.netty.handler.ssl.SslProvider.OPENSSL)
-                            .trustManager(clientCert)
-                            .build();
-                } catch (SSLException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-
-            private SslContext newServerContext() {
-                try {
-                    File serverCert = Util.loadCert("server1.pem");
-                    File serverKey = Util.loadCert("server1.key");
-                    return SslContextBuilder.forServer(serverCert, serverKey)
-                            .sslProvider(io.netty.handler.ssl.SslProvider.OPENSSL)
-                            .build();
-                } catch (SSLException e) {
-                    throw new RuntimeException(e);
-                }
-            }
         };
 
         abstract SSLEngine newClientEngine(String cipher);
         abstract SSLEngine newServerEngine(String cipher);
-
-        final SSLContext initContext(SSLContext context, boolean client) {
-            if (client) {
-                File cert = Util.loadCert("ca.pem");
-                context = Util.initClientSslContext(context, cert);
-            } else {
-                File cert = Util.loadCert("server1.pem");
-                File key = Util.loadCert("server1.key");
-                context = Util.initServerContext(context, cert, key);
-            }
-            return context;
-        }
-
-        final SSLEngine initEngine(SSLEngine engine, String cipher, boolean client) {
-            engine.setEnabledProtocols(new String[] {PROTOCOL_TLS_V1_2});
-            engine.setEnabledCipherSuites(new String[] {cipher});
-            engine.setUseClientMode(client);
-            return engine;
-        }
     }
 
     public enum BufferType {
@@ -195,12 +169,10 @@
         encryptedBuffer = bufferType.newBuffer(clientEngine.getSession().getPacketBufferSize());
 
         // Generate the message to be sent from the client.
-        clientCleartextBuffer = bufferType.newBuffer(messageSize);
         serverCleartextBuffer = bufferType.newBuffer(
                 max(messageSize, serverEngine.getSession().getApplicationBufferSize()));
-        for (int i = 0; clientCleartextBuffer.hasRemaining(); i = (i + 1) % CHARS.length) {
-            clientCleartextBuffer.put(CHARS[i]);
-        }
+        clientCleartextBuffer = bufferType.newBuffer(messageSize);
+        clientCleartextBuffer.put(newTextMessage(messageSize));
         clientCleartextBuffer.flip();
 
         // Complete the initial TLS handshake.
diff --git a/openjdk-benchmarks/src/main/java/org/conscrypt/benchmarks/EchoServer.java b/openjdk-benchmarks/src/main/java/org/conscrypt/benchmarks/EchoServer.java
new file mode 100644
index 0000000..25edaf6
--- /dev/null
+++ b/openjdk-benchmarks/src/main/java/org/conscrypt/benchmarks/EchoServer.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.conscrypt.benchmarks;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Simple echo server that responds with an identical message to the one received.
+ */
+final class EchoServer {
+    private final ExecutorService executor = Executors.newSingleThreadExecutor();
+    private final SSLServerSocket serverSocket;
+    private final int messageSize;
+    private final byte[] buffer;
+    private SSLSocket socket;
+    private volatile boolean stopping;
+
+    EchoServer(SSLServerSocket serverSocket, int messageSize) {
+        this.serverSocket = serverSocket;
+        this.messageSize = messageSize;
+        buffer = new byte[messageSize];
+    }
+
+    Future<?> start() {
+        return executor.submit(new AcceptTask());
+    }
+
+    void stop() {
+        try {
+            stopping = true;
+            if (socket != null) {
+                socket.close();
+            }
+            serverSocket.close();
+            executor.shutdown();
+            executor.awaitTermination(5, TimeUnit.SECONDS);
+        } catch (IOException | InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    int port() {
+        return serverSocket.getLocalPort();
+    }
+
+    private final class AcceptTask implements Runnable {
+        @Override
+        public void run() {
+            try {
+                if (stopping) {
+                    return;
+                }
+                socket = (SSLSocket) serverSocket.accept();
+
+                if (stopping) {
+                    return;
+                }
+                executor.execute(new ReadTask());
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private final class ReadTask implements Runnable {
+        @Override
+        public void run() {
+            try {
+                if (stopping) {
+                    return;
+                }
+                byte[] output = readMessage();
+                sendMessage(output);
+
+                if (stopping) {
+                    return;
+                }
+                // Keep running the task until it's being shut down.
+                executor.execute(this);
+            } catch (Throwable e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private byte[] readMessage() {
+        try {
+            int totalBytesRead = 0;
+            while (totalBytesRead < messageSize) {
+                int remaining = messageSize - totalBytesRead;
+                int bytesRead = socket.getInputStream().read(buffer, totalBytesRead, remaining);
+                if (bytesRead == -1) {
+                    break;
+                }
+                totalBytesRead += bytesRead;
+            }
+            return Arrays.copyOfRange(buffer, 0, totalBytesRead);
+        } catch (Throwable e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void sendMessage(byte[] data) {
+        try {
+            socket.getOutputStream().write(data);
+            socket.getOutputStream().flush();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/openjdk-benchmarks/src/main/java/org/conscrypt/benchmarks/NettyEchoServer.java b/openjdk-benchmarks/src/main/java/org/conscrypt/benchmarks/NettyEchoServer.java
new file mode 100644
index 0000000..947d78f
--- /dev/null
+++ b/openjdk-benchmarks/src/main/java/org/conscrypt/benchmarks/NettyEchoServer.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.conscrypt.benchmarks;
+
+import static io.netty.channel.ChannelOption.SO_BACKLOG;
+import static io.netty.channel.ChannelOption.SO_KEEPALIVE;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslHandler;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import javax.net.ssl.SSLEngine;
+
+/**
+ * A test server based on Netty and Netty-tcnative that auto-replies with every message
+ * it receives.
+ */
+final class NettyEchoServer {
+    private final EventLoopGroup group = new NioEventLoopGroup();
+    private final int port;
+    private final int messageSize;
+    private Channel channel;
+    private String cipher;
+
+    NettyEchoServer(int port, int messageSize, String cipher) {
+        this.port = port;
+        this.messageSize = messageSize;
+        this.cipher = cipher;
+    }
+
+    void start() {
+        ServerBootstrap b = new ServerBootstrap();
+        b.group(group);
+        b.channel(NioServerSocketChannel.class);
+        b.option(SO_BACKLOG, 128);
+        b.childOption(SO_KEEPALIVE, true);
+        b.childHandler(new ChannelInitializer<Channel>() {
+            @Override
+            public void initChannel(final Channel ch) throws Exception {
+                SslContext context = Util.newNettyServerContext(cipher);
+                SSLEngine sslEngine = context.newEngine(ch.alloc());
+                ch.pipeline().addFirst(new SslHandler(sslEngine));
+                ch.pipeline().addLast(new EchoService());
+            }
+        });
+        // Bind and start to accept incoming connections.
+        ChannelFuture future = b.bind(port);
+        try {
+            future.await();
+        } catch (InterruptedException ex) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("Interrupted waiting for bind");
+        }
+        if (!future.isSuccess()) {
+            throw new RuntimeException("Failed to bind", future.cause());
+        }
+        channel = future.channel();
+    }
+
+    void stop() {
+        if (channel != null) {
+            channel.close().awaitUninterruptibly();
+            group.shutdownGracefully(1, 5, TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Handler that automatically responds with ever message it receives.
+     */
+    private final class EchoService extends ByteToMessageDecoder {
+        @Override
+        protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
+                throws Exception {
+            if (in.readableBytes() >= messageSize) {
+                // Copy the input to a new direct buffer.
+                ByteBuf response = ctx.alloc().directBuffer(messageSize);
+                response.writeBytes(in, in.readerIndex(), messageSize);
+                in.skipBytes(messageSize);
+
+                ctx.writeAndFlush(response);
+            }
+        }
+    }
+}
diff --git a/openjdk-benchmarks/src/main/java/org/conscrypt/benchmarks/TestClient.java b/openjdk-benchmarks/src/main/java/org/conscrypt/benchmarks/TestClient.java
new file mode 100644
index 0000000..bbeeae4
--- /dev/null
+++ b/openjdk-benchmarks/src/main/java/org/conscrypt/benchmarks/TestClient.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.conscrypt.benchmarks;
+
+import java.io.IOException;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Client-side endpoint. Provides basic services for sending/receiving messages from the client
+ * socket.
+ */
+final class TestClient {
+    private final SSLSocket socket;
+
+    TestClient(SSLSocket socket) {
+        this.socket = socket;
+    }
+
+    void start() {
+        try {
+            socket.startHandshake();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    void stop() {
+        try {
+            socket.close();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    int readMessage(byte[] buffer) {
+        try {
+            int totalBytesRead = 0;
+            while (totalBytesRead < buffer.length) {
+                int remaining = buffer.length - totalBytesRead;
+                int bytesRead = socket.getInputStream().read(buffer, totalBytesRead, remaining);
+                if (bytesRead == -1) {
+                    break;
+                }
+                totalBytesRead += bytesRead;
+            }
+            return totalBytesRead;
+        } catch (Throwable e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    void sendMessage(byte[] data) {
+        try {
+            socket.getOutputStream().write(data);
+            socket.getOutputStream().flush();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/openjdk-benchmarks/src/main/java/org/conscrypt/benchmarks/Util.java b/openjdk-benchmarks/src/main/java/org/conscrypt/benchmarks/Util.java
index ba6c20a..eb5172b 100644
--- a/openjdk-benchmarks/src/main/java/org/conscrypt/benchmarks/Util.java
+++ b/openjdk-benchmarks/src/main/java/org/conscrypt/benchmarks/Util.java
@@ -19,6 +19,8 @@
 import com.google.common.base.Charsets;
 import com.google.common.io.BaseEncoding;
 import com.google.common.io.CharStreams;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
 import java.io.BufferedInputStream;
 import java.io.BufferedWriter;
 import java.io.File;
@@ -28,35 +30,169 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
+import java.net.ServerSocket;
 import java.security.KeyException;
 import java.security.KeyFactory;
 import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.Security;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.security.spec.KeySpec;
 import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Collections;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.TrustManagerFactory;
 import javax.security.auth.x500.X500Principal;
+import org.conscrypt.OpenSSLProvider;
+import org.conscrypt.OpenSSLSocketFactoryImpl;
 
 /**
  * Utility methods to support testing.
  */
 final class Util {
+    private static final Provider JDK_PROVIDER = getDefaultTlsProvider();
+    private static final Provider CONSCRYPT_PROVIDER = new OpenSSLProvider();
+    private static final byte[] CHARS =
+            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".getBytes();
     private static final Pattern KEY_PATTERN =
             Pattern.compile("-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" + // Header
                             "([a-z0-9+/=\\r\\n]+)" + // Base64 text
                             "-+END\\s+.*PRIVATE\\s+KEY[^-]*-+", // Footer
                     Pattern.CASE_INSENSITIVE);
 
+    static final String PROTOCOL_TLS_V1_2 = "TLSv1.2";
+    static final String PROVIDER_PROPERTY = "SSLContext.TLSv1.2";
+    static final String LOCALHOST = "localhost";
+
     private Util() {}
 
     /**
+     * Returns an array containing only {@link #PROTOCOL_TLS_V1_2}.
+     */
+    static String[] getProtocols() {
+        return new String[] {PROTOCOL_TLS_V1_2};
+    }
+
+    static SSLSocketFactory getJdkSocketFactory() {
+        return getSocketFactory(JDK_PROVIDER);
+    }
+
+    static SSLServerSocketFactory getJdkServerSocketFactory() {
+        return getServerSocketFactory(JDK_PROVIDER);
+    }
+
+    static SSLSocketFactory getConscryptSocketFactory(boolean useEngineSocket) {
+        OpenSSLSocketFactoryImpl socketFactory =
+                (OpenSSLSocketFactoryImpl) getSocketFactory(CONSCRYPT_PROVIDER);
+        socketFactory.setUseEngineSocket(useEngineSocket);
+        return socketFactory;
+    }
+
+    static SSLServerSocketFactory getConscryptServerSocketFactory() {
+        return getServerSocketFactory(CONSCRYPT_PROVIDER);
+    }
+
+    private static SSLSocketFactory getSocketFactory(Provider provider) {
+        SSLContext clientContext = initClientSslContext(newContext(provider));
+        return clientContext.getSocketFactory();
+    }
+
+    private static SSLServerSocketFactory getServerSocketFactory(Provider provider) {
+        SSLContext serverContext = initServerContext(newContext(provider));
+        return serverContext.getServerSocketFactory();
+    }
+
+    static SSLContext newContext(Provider provider) {
+        try {
+            return SSLContext.getInstance("TLS", provider);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    static SslContext newNettyClientContext(String cipher) {
+        try {
+            File clientCert = loadCert("ca.pem");
+            SslContextBuilder ctx = SslContextBuilder.forClient()
+                                            .sslProvider(io.netty.handler.ssl.SslProvider.OPENSSL)
+                                            .trustManager(clientCert);
+            if (cipher != null) {
+                ctx.ciphers(Collections.singletonList(cipher));
+            }
+            return ctx.build();
+        } catch (SSLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    static SslContext newNettyServerContext(String cipher) {
+        try {
+            File serverCert = loadCert("server1.pem");
+            File serverKey = loadCert("server1.key");
+            SslContextBuilder ctx = SslContextBuilder.forServer(serverCert, serverKey)
+                                            .sslProvider(io.netty.handler.ssl.SslProvider.OPENSSL);
+            if (cipher != null) {
+                ctx.ciphers(Collections.singletonList(cipher));
+            }
+            return ctx.build();
+        } catch (SSLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Picks a port that is not used right at this moment.
+     * Warning: Not thread safe. May see "BindException: Address already in use: bind" if using the
+     * returned port to create a new server socket when other threads/processes are concurrently
+     * creating new sockets without a specific port.
+     */
+    static int pickUnusedPort() {
+        try {
+            ServerSocket serverSocket = new ServerSocket(0);
+            int port = serverSocket.getLocalPort();
+            serverSocket.close();
+            return port;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Creates a text message of the given length.
+     */
+    static byte[] newTextMessage(int length) {
+        byte[] msg = new byte[length];
+        for (int msgIndex = 0; msgIndex < length; ) {
+            int remaining = length - msgIndex;
+            int numChars = Math.min(remaining, CHARS.length);
+            System.arraycopy(CHARS, 0, msg, msgIndex, numChars);
+            msgIndex += numChars;
+        }
+        return msg;
+    }
+
+    /**
+     * Initializes the given engine with the cipher and client mode.
+     */
+    static SSLEngine initEngine(SSLEngine engine, String cipher, boolean client) {
+        engine.setEnabledProtocols(getProtocols());
+        engine.setEnabledCipherSuites(new String[] {cipher});
+        engine.setUseClientMode(client);
+        return engine;
+    }
+
+    /**
      * Load a file from the resources folder.
      *
      * @param name  name of a file in src/main/resources/certs.
@@ -84,9 +220,16 @@
     }
 
     /**
+     * Initializes the given client-side {@code context} with a default cert.
+     */
+    static SSLContext initClientSslContext(SSLContext context) {
+        File cert = loadCert("ca.pem");
+        return initClientSslContext(context, cert);
+    }
+
+    /**
      * Initializes the given client-side {@code context} with an appropriate trust manager based on
-     * the
-     * {@code certChainFile} as its only root certificate.
+     * the {@code certChainFile} as its only root certificate.
      */
     static SSLContext initClientSslContext(SSLContext context, File certChainFile) {
         try {
@@ -110,7 +253,16 @@
     }
 
     /**
-     * Initializes the given server-side {@code context} with the
+     * Initializes the given server-side {@code context} with the default cert chain and key.
+     */
+    static SSLContext initServerContext(SSLContext context) {
+        File cert = loadCert("server1.pem");
+        File key = loadCert("server1.key");
+        return initServerContext(context, cert, key);
+    }
+
+    /**
+     * Initializes the given server-side {@code context} with the given cert chain and private key.
      */
     static SSLContext initServerContext(SSLContext context, File certChainFile, File keyFile) {
         try {
@@ -178,4 +330,13 @@
             }
         }
     }
+
+    private static Provider getDefaultTlsProvider() {
+        for (Provider p : Security.getProviders()) {
+            if (p.get(PROVIDER_PROPERTY) != null) {
+                return p;
+            }
+        }
+        throw new RuntimeException("Unable to find a default provider for " + PROVIDER_PROPERTY);
+    }
 }
diff --git a/openjdk/src/main/java/org/conscrypt/NativeCryptoJni.java b/openjdk/src/main/java/org/conscrypt/NativeCryptoJni.java
index 7e34f84..0076579 100644
--- a/openjdk/src/main/java/org/conscrypt/NativeCryptoJni.java
+++ b/openjdk/src/main/java/org/conscrypt/NativeCryptoJni.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 The Android Open Source Project
+ * Copyright 2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/openjdk/src/main/java/org/conscrypt/NativeLibraryLoader.java b/openjdk/src/main/java/org/conscrypt/NativeLibraryLoader.java
index 31a62eb..e1d46cc 100644
--- a/openjdk/src/main/java/org/conscrypt/NativeLibraryLoader.java
+++ b/openjdk/src/main/java/org/conscrypt/NativeLibraryLoader.java
@@ -1,4 +1,20 @@
 /*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
  * Copyright 2014 The Netty Project
  *
  * The Netty Project licenses this file to you under the Apache License,
@@ -15,8 +31,8 @@
  */
 package org.conscrypt;
 
-import java.io.Closeable;
 import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
diff --git a/openjdk/src/main/java/org/conscrypt/NativeLibraryUtil.java b/openjdk/src/main/java/org/conscrypt/NativeLibraryUtil.java
index d69b0d6..547fc57 100644
--- a/openjdk/src/main/java/org/conscrypt/NativeLibraryUtil.java
+++ b/openjdk/src/main/java/org/conscrypt/NativeLibraryUtil.java
@@ -1,5 +1,21 @@
 /*
- * Copyright 2016 The Netty Project
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright 2017 The Netty Project
  *
  * The Netty Project licenses this file to you under the Apache License,
  * version 2.0 (the "License"); you may not use this file except in compliance