Conformance fixes for the engine-based socket. (#202)

This allows the SSLSocketTest to pass with the engine-based socket
enabled.

Also restructuring the inheritance hierarchy so that the FD and engine
sockets both behave the same way (both either wrappers or not). The
restructure involves the following:

- AbstractConscryptSocket: New base class for both sockets. It handles
the wrap/no-wrap logic.

- OpenSSLSocketImplWrapper: deleted and replaced by
AbstractConscryptSocket.

- OpenSSLSocketImpl: reduced to a public shim class between
AbstractConscryptSocket and the implementations. For backward-compat
only.

- ConscryptFileDescriptorSocket: Renamed from OpenSSLSocketImpl. The
old FD socket.
diff --git a/android-stub/src/main/java/com/android/org/conscrypt/NativeCrypto.java b/android-stub/src/main/java/com/android/org/conscrypt/NativeCrypto.java
index 831dd17..851e27b 100644
--- a/android-stub/src/main/java/com/android/org/conscrypt/NativeCrypto.java
+++ b/android-stub/src/main/java/com/android/org/conscrypt/NativeCrypto.java
@@ -20,6 +20,7 @@
 import java.security.cert.CertificateException;
 import javax.net.ssl.SSLException;
 
+@SuppressWarnings("unused")
 final class NativeCrypto {
     public interface SSLHandshakeCallbacks {
         /**
diff --git a/android-stub/src/main/java/com/android/org/conscrypt/SSLParametersImpl.java b/android-stub/src/main/java/com/android/org/conscrypt/SSLParametersImpl.java
index 079f89f..651d431 100644
--- a/android-stub/src/main/java/com/android/org/conscrypt/SSLParametersImpl.java
+++ b/android-stub/src/main/java/com/android/org/conscrypt/SSLParametersImpl.java
@@ -16,7 +16,7 @@
 
 package com.android.org.conscrypt;
 
-class SSLParametersImpl {
+final class SSLParametersImpl {
     public static SSLParametersImpl getDefault() {
         throw new RuntimeException("Stub!");
     }
diff --git a/android/proguard-rules.pro b/android/proguard-rules.pro
index c3bdd2c..3bc75b2 100644
--- a/android/proguard-rules.pro
+++ b/android/proguard-rules.pro
@@ -20,5 +20,7 @@
 -dontwarn dalvik.system.BlockGuard
 -dontwarn dalvik.system.BlockGuard$Policy
 -dontwarn dalvik.system.CloseGuard
+-dontwarn com.android.org.conscrypt.AbstractConscryptSocket
+-dontwarn com.android.org.conscrypt.ConscryptFileDescriptorSocket
 -dontwarn com.android.org.conscrypt.OpenSSLSocketImpl
 -dontwarn org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl
diff --git a/android/src/main/java/org/conscrypt/KitKatPlatformOpenSSLSocketImplAdapter.java b/android/src/main/java/org/conscrypt/KitKatPlatformOpenSSLSocketImplAdapter.java
index 556b815..db7dc85 100644
--- a/android/src/main/java/org/conscrypt/KitKatPlatformOpenSSLSocketImplAdapter.java
+++ b/android/src/main/java/org/conscrypt/KitKatPlatformOpenSSLSocketImplAdapter.java
@@ -45,9 +45,9 @@
         extends com.android.org.conscrypt.OpenSSLSocketImpl {
 
 
-    private final org.conscrypt.OpenSSLSocketImpl delegate;
+    private final AbstractConscryptSocket delegate;
 
-    public KitKatPlatformOpenSSLSocketImplAdapter(org.conscrypt.OpenSSLSocketImpl delegate)
+    public KitKatPlatformOpenSSLSocketImplAdapter(AbstractConscryptSocket delegate)
             throws IOException {
         super(null);
         this.delegate = delegate;
diff --git a/android/src/main/java/org/conscrypt/Platform.java b/android/src/main/java/org/conscrypt/Platform.java
index e7663ae..be8eba6 100644
--- a/android/src/main/java/org/conscrypt/Platform.java
+++ b/android/src/main/java/org/conscrypt/Platform.java
@@ -82,9 +82,8 @@
         }
     }
 
-    public static FileDescriptor getFileDescriptorFromSSLSocket(
-            OpenSSLSocketImpl openSSLSocketImpl) {
-        return getFileDescriptor(openSSLSocketImpl);
+    public static FileDescriptor getFileDescriptorFromSSLSocket(AbstractConscryptSocket socket) {
+        return getFileDescriptor(socket);
     }
 
     public static String getCurveName(ECParameterSpec spec) {
@@ -195,7 +194,7 @@
     }
 
     public static void setSSLParameters(
-            SSLParameters params, SSLParametersImpl impl, OpenSSLSocketImpl socket) {
+            SSLParameters params, SSLParametersImpl impl, AbstractConscryptSocket socket) {
         try {
             setSSLParametersOnImpl(params, impl);
 
@@ -260,7 +259,7 @@
     }
 
     public static void getSSLParameters(
-            SSLParameters params, SSLParametersImpl impl, OpenSSLSocketImpl socket) {
+            SSLParameters params, SSLParametersImpl impl, AbstractConscryptSocket socket) {
         try {
             getSSLParametersFromImpl(params, impl);
 
@@ -276,7 +275,7 @@
 
     @TargetApi(24)
     private static void setParametersSniHostname(
-            SSLParameters params, SSLParametersImpl impl, OpenSSLSocketImpl socket)
+            SSLParameters params, SSLParametersImpl impl, AbstractConscryptSocket socket)
             throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
         if (impl.getUseSni() && AddressUtils.isValidSniHostname(socket.getHostname())) {
             Method m_setServerNames = params.getClass().getMethod("setServerNames", List.class);
@@ -360,9 +359,9 @@
         return false;
     }
 
-    @SuppressLint("NewApi") // OpenSSLSocketImpl defines getHandshakeSession()
+    @SuppressLint("NewApi") // AbstractConscryptSocket defines getHandshakeSession()
     public static void checkClientTrusted(X509TrustManager tm, X509Certificate[] chain,
-            String authType, OpenSSLSocketImpl socket) throws CertificateException {
+            String authType, AbstractConscryptSocket socket) throws CertificateException {
         if (!checkTrusted("checkClientTrusted", tm, chain, authType, Socket.class, socket)
                 && !checkTrusted("checkClientTrusted", tm, chain, authType, String.class,
                            socket.getHandshakeSession().getPeerHost())) {
@@ -370,9 +369,9 @@
         }
     }
 
-    @SuppressLint("NewApi") // OpenSSLSocketImpl defines getHandshakeSession()
+    @SuppressLint("NewApi") // AbstractConscryptSocket defines getHandshakeSession()
     public static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain,
-            String authType, OpenSSLSocketImpl socket) throws CertificateException {
+            String authType, AbstractConscryptSocket socket) throws CertificateException {
         if (!checkTrusted("checkServerTrusted", tm, chain, authType, Socket.class, socket)
                 && !checkTrusted("checkServerTrusted", tm, chain, authType, String.class,
                            socket.getHandshakeSession().getPeerHost())) {
@@ -380,7 +379,7 @@
         }
     }
 
-    @SuppressLint("NewApi") // OpenSSLSocketImpl defines getHandshakeSession()
+    @SuppressLint("NewApi") // AbstractConscryptSocket defines getHandshakeSession()
     public static void checkClientTrusted(X509TrustManager tm, X509Certificate[] chain,
             String authType, ConscryptEngine engine) throws CertificateException {
         if (!checkTrusted("checkClientTrusted", tm, chain, authType, SSLEngine.class, engine)
@@ -390,7 +389,7 @@
         }
     }
 
-    @SuppressLint("NewApi") // OpenSSLSocketImpl defines getHandshakeSession()
+    @SuppressLint("NewApi") // AbstractConscryptSocket defines getHandshakeSession()
     public static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain,
             String authType, ConscryptEngine engine) throws CertificateException {
         if (!checkTrusted("checkServerTrusted", tm, chain, authType, SSLEngine.class, engine)
diff --git a/android/src/main/java/org/conscrypt/PreKitKatPlatformOpenSSLSocketImplAdapter.java b/android/src/main/java/org/conscrypt/PreKitKatPlatformOpenSSLSocketImplAdapter.java
index 1a89208..9353a91 100644
--- a/android/src/main/java/org/conscrypt/PreKitKatPlatformOpenSSLSocketImplAdapter.java
+++ b/android/src/main/java/org/conscrypt/PreKitKatPlatformOpenSSLSocketImplAdapter.java
@@ -45,9 +45,9 @@
         extends org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl {
 
 
-    private final org.conscrypt.OpenSSLSocketImpl delegate;
+    private final AbstractConscryptSocket delegate;
 
-    public PreKitKatPlatformOpenSSLSocketImplAdapter(org.conscrypt.OpenSSLSocketImpl delegate)
+    public PreKitKatPlatformOpenSSLSocketImplAdapter(AbstractConscryptSocket delegate)
             throws IOException {
         super(null);
         this.delegate = delegate;
diff --git a/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java b/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java
new file mode 100644
index 0000000..df6fbaa
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java
@@ -0,0 +1,697 @@
+/*
+ * Copyright (C) 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;
+
+import static org.conscrypt.Preconditions.checkArgument;
+import static org.conscrypt.Preconditions.checkNotNull;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.channels.SocketChannel;
+import java.security.PrivateKey;
+import java.util.ArrayList;
+import java.util.List;
+import javax.net.ssl.HandshakeCompletedEvent;
+import javax.net.ssl.HandshakeCompletedListener;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Abstract base class for all Conscrypt sockets that extends the basic {@link SSLSocket} API.
+ */
+abstract class AbstractConscryptSocket extends SSLSocket {
+    final Socket socket;
+    private final boolean autoClose;
+
+    /**
+     * The peer's DNS hostname if it was supplied during creation. Note that
+     * this may be a raw IP address, so it should be checked before use with
+     * extensions that don't use it like Server Name Indication (SNI).
+     */
+    private String peerHostname;
+
+    /**
+     * The peer's port if it was supplied during creation. Should only be set if
+     * {@link #peerHostname} is also set.
+     */
+    private final int peerPort;
+
+    private final PeerInfoProvider peerInfoProvider = new PeerInfoProvider() {
+        @Override
+        String getHostname() {
+            return AbstractConscryptSocket.this.getHostname();
+        }
+
+        @Override
+        String getHostnameOrIP() {
+            return AbstractConscryptSocket.this.getHostnameOrIP();
+        }
+
+        @Override
+        int getPort() {
+            return AbstractConscryptSocket.this.getPort();
+        }
+    };
+
+    private final List<HandshakeCompletedListener> listeners =
+            new ArrayList<HandshakeCompletedListener>(2);
+
+    /**
+     * Local cache of timeout to avoid getsockopt on every read and
+     * write for non-wrapped sockets. Note that this is not used when delegating
+     * to another socket.
+     */
+    private int readTimeoutMilliseconds;
+
+    AbstractConscryptSocket() throws IOException {
+        this.socket = this;
+        this.peerHostname = null;
+        this.peerPort = -1;
+        this.autoClose = false;
+    }
+
+    AbstractConscryptSocket(String hostname, int port) throws IOException {
+        super(hostname, port);
+        this.socket = this;
+        this.peerHostname = hostname;
+        this.peerPort = port;
+        this.autoClose = false;
+    }
+
+    AbstractConscryptSocket(InetAddress address, int port) throws IOException {
+        super(address, port);
+        this.socket = this;
+        this.peerHostname = null;
+        this.peerPort = -1;
+        this.autoClose = false;
+    }
+
+    AbstractConscryptSocket(String hostname, int port, InetAddress clientAddress, int clientPort)
+            throws IOException {
+        super(hostname, port, clientAddress, clientPort);
+        this.socket = this;
+        this.peerHostname = hostname;
+        this.peerPort = port;
+        this.autoClose = false;
+    }
+
+    AbstractConscryptSocket(InetAddress address, int port, InetAddress clientAddress,
+            int clientPort) throws IOException {
+        super(address, port, clientAddress, clientPort);
+        this.socket = this;
+        this.peerHostname = null;
+        this.peerPort = -1;
+        this.autoClose = false;
+    }
+
+    AbstractConscryptSocket(Socket socket, String hostname, int port, boolean autoClose)
+            throws IOException {
+        this.socket = checkNotNull(socket, "socket");
+        this.peerHostname = hostname;
+        this.peerPort = port;
+        this.autoClose = autoClose;
+    }
+
+    @Override
+    public final void connect(SocketAddress endpoint) throws IOException {
+        connect(endpoint, 0);
+    }
+
+    /**
+     * Try to extract the peer's hostname if it's available from the endpoint address.
+     */
+    @Override
+    public final void connect(SocketAddress endpoint, int timeout) throws IOException {
+        if (peerHostname == null && endpoint instanceof InetSocketAddress) {
+            peerHostname =
+                    Platform.getHostStringFromInetSocketAddress((InetSocketAddress) endpoint);
+        }
+
+        if (isDelegating()) {
+            socket.connect(endpoint, timeout);
+        } else {
+            super.connect(endpoint, timeout);
+        }
+    }
+
+    @Override
+    public void bind(SocketAddress bindpoint) throws IOException {
+        if (isDelegating()) {
+            socket.bind(bindpoint);
+        } else {
+            super.bind(bindpoint);
+        }
+    }
+
+    @Override
+    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
+    public void close() throws IOException {
+        if (isDelegating()) {
+            if (autoClose && !socket.isClosed()) {
+                socket.close();
+            }
+        } else {
+            if (!super.isClosed()) {
+                super.close();
+            }
+        }
+    }
+
+    @Override
+    public InetAddress getInetAddress() {
+        if (isDelegating()) {
+            return socket.getInetAddress();
+        }
+        return super.getInetAddress();
+    }
+
+    @Override
+    public InetAddress getLocalAddress() {
+        if (isDelegating()) {
+            return socket.getLocalAddress();
+        }
+        return super.getLocalAddress();
+    }
+
+    @Override
+    public int getLocalPort() {
+        if (isDelegating()) {
+            return socket.getLocalPort();
+        }
+        return super.getLocalPort();
+    }
+
+    @Override
+    public SocketAddress getRemoteSocketAddress() {
+        if (isDelegating()) {
+            return socket.getRemoteSocketAddress();
+        }
+        return super.getRemoteSocketAddress();
+    }
+
+    @Override
+    public SocketAddress getLocalSocketAddress() {
+        if (isDelegating()) {
+            return socket.getLocalSocketAddress();
+        }
+        return super.getLocalSocketAddress();
+    }
+
+    @Override
+    public final int getPort() {
+        if (isDelegating()) {
+            return socket.getPort();
+        }
+
+        if (peerPort != -1) {
+            // Return the port that has been explicitly set in the constructor.
+            return peerPort;
+        }
+        return super.getPort();
+    }
+
+    @Override
+    public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
+        checkArgument(listener != null, "Provided listener is null");
+        listeners.add(listener);
+    }
+
+    @Override
+    public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
+        checkArgument(listener != null, "Provided listener is null");
+        if (!listeners.remove(listener)) {
+            throw new IllegalArgumentException("Provided listener is not registered");
+        }
+    }
+
+    /* @Override */
+    @SuppressWarnings("MissingOverride") // For compilation with Java 6.
+    public abstract SSLSession getHandshakeSession();
+
+    /* @Override */
+    public FileDescriptor getFileDescriptor$() {
+        if (isDelegating()) {
+            return Platform.getFileDescriptor(socket);
+        }
+        return Platform.getFileDescriptorFromSSLSocket(this);
+    }
+
+    @Override
+    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
+    public final void setSoTimeout(int readTimeoutMilliseconds) throws SocketException {
+        if (isDelegating()) {
+            socket.setSoTimeout(readTimeoutMilliseconds);
+        } else {
+            super.setSoTimeout(readTimeoutMilliseconds);
+            this.readTimeoutMilliseconds = readTimeoutMilliseconds;
+        }
+    }
+
+    @Override
+    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
+    public final int getSoTimeout() throws SocketException {
+        if (isDelegating()) {
+            return socket.getSoTimeout();
+        }
+        return readTimeoutMilliseconds;
+    }
+
+    @Override
+    public final void sendUrgentData(int data) throws IOException {
+        throw new SocketException("Method sendUrgentData() is not supported.");
+    }
+
+    @Override
+    public final void setOOBInline(boolean on) throws SocketException {
+        throw new SocketException("Method setOOBInline() is not supported.");
+    }
+
+    @Override
+    public boolean getOOBInline() throws SocketException {
+        return false;
+    }
+
+    @Override
+    public SocketChannel getChannel() {
+        // TODO(nmittler): Support channels?
+        return null;
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+        if (isDelegating()) {
+            return socket.getInputStream();
+        }
+        return super.getInputStream();
+    }
+
+    @Override
+    public OutputStream getOutputStream() throws IOException {
+        if (isDelegating()) {
+            return socket.getOutputStream();
+        }
+        return super.getOutputStream();
+    }
+
+    @Override
+    public void setTcpNoDelay(boolean on) throws SocketException {
+        if (isDelegating()) {
+            socket.setTcpNoDelay(on);
+        } else {
+            super.setTcpNoDelay(on);
+        }
+    }
+
+    @Override
+    public boolean getTcpNoDelay() throws SocketException {
+        if (isDelegating()) {
+            return socket.getTcpNoDelay();
+        }
+        return super.getTcpNoDelay();
+    }
+
+    @Override
+    public void setSoLinger(boolean on, int linger) throws SocketException {
+        if (isDelegating()) {
+            socket.setSoLinger(on, linger);
+        } else {
+            super.setSoLinger(on, linger);
+        }
+    }
+
+    @Override
+    public int getSoLinger() throws SocketException {
+        if (isDelegating()) {
+            return socket.getSoLinger();
+        }
+        return super.getSoLinger();
+    }
+
+    @Override
+    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
+    public void setSendBufferSize(int size) throws SocketException {
+        if (isDelegating()) {
+            socket.setSendBufferSize(size);
+        } else {
+            super.setSendBufferSize(size);
+        }
+    }
+
+    @Override
+    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
+    public int getSendBufferSize() throws SocketException {
+        if (isDelegating()) {
+            return socket.getSendBufferSize();
+        }
+        return super.getSendBufferSize();
+    }
+
+    @Override
+    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
+    public void setReceiveBufferSize(int size) throws SocketException {
+        if (isDelegating()) {
+            socket.setReceiveBufferSize(size);
+        } else {
+            super.setReceiveBufferSize(size);
+        }
+    }
+
+    @Override
+    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
+    public int getReceiveBufferSize() throws SocketException {
+        if (isDelegating()) {
+            return socket.getReceiveBufferSize();
+        }
+        return super.getReceiveBufferSize();
+    }
+
+    @Override
+    public void setKeepAlive(boolean on) throws SocketException {
+        if (isDelegating()) {
+            socket.setKeepAlive(on);
+        } else {
+            super.setKeepAlive(on);
+        }
+    }
+
+    @Override
+    public boolean getKeepAlive() throws SocketException {
+        if (isDelegating()) {
+            return socket.getKeepAlive();
+        }
+        return super.getKeepAlive();
+    }
+
+    @Override
+    public void setTrafficClass(int tc) throws SocketException {
+        if (isDelegating()) {
+            socket.setTrafficClass(tc);
+        } else {
+            super.setTrafficClass(tc);
+        }
+    }
+
+    @Override
+    public int getTrafficClass() throws SocketException {
+        if (isDelegating()) {
+            return socket.getTrafficClass();
+        }
+        return super.getTrafficClass();
+    }
+
+    @Override
+    public void setReuseAddress(boolean on) throws SocketException {
+        if (isDelegating()) {
+            socket.setReuseAddress(on);
+        } else {
+            super.setReuseAddress(on);
+        }
+    }
+
+    @Override
+    public boolean getReuseAddress() throws SocketException {
+        if (isDelegating()) {
+            return socket.getReuseAddress();
+        }
+        return super.getReuseAddress();
+    }
+
+    @Override
+    public void shutdownInput() throws IOException {
+        if (isDelegating()) {
+            socket.shutdownInput();
+        } else {
+            super.shutdownInput();
+        }
+    }
+
+    @Override
+    public void shutdownOutput() throws IOException {
+        if (isDelegating()) {
+            socket.shutdownOutput();
+        } else {
+            super.shutdownOutput();
+        }
+    }
+
+    @Override
+    public boolean isConnected() {
+        if (isDelegating()) {
+            return socket.isConnected();
+        }
+        return super.isConnected();
+    }
+
+    @Override
+    public boolean isBound() {
+        if (isDelegating()) {
+            return socket.isBound();
+        }
+        return super.isBound();
+    }
+
+    @Override
+    public boolean isClosed() {
+        if (isDelegating()) {
+            return socket.isClosed();
+        }
+        return super.isClosed();
+    }
+
+    @Override
+    public boolean isInputShutdown() {
+        if (isDelegating()) {
+            return socket.isInputShutdown();
+        }
+        return super.isInputShutdown();
+    }
+
+    @Override
+    public boolean isOutputShutdown() {
+        if (isDelegating()) {
+            return socket.isOutputShutdown();
+        }
+        return super.isOutputShutdown();
+    }
+
+    @Override
+    public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
+        if (isDelegating()) {
+            socket.setPerformancePreferences(connectionTime, latency, bandwidth);
+        } else {
+            super.setPerformancePreferences(connectionTime, latency, bandwidth);
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder("SSL socket over ");
+        if (isDelegating()) {
+            builder.append(socket.toString());
+        } else {
+            builder.append(super.toString());
+        }
+        return builder.toString();
+    }
+
+    /**
+     * Returns the hostname that was supplied during socket creation. No DNS resolution is
+     * attempted before returning the hostname.
+     */
+    String getHostname() {
+        return peerHostname;
+    }
+
+    /**
+     * This method enables Server Name Indication
+     *
+     * @param hostname the desired SNI hostname, or null to disable
+     */
+    void setHostname(String hostname) {
+        peerHostname = hostname;
+    }
+
+    /**
+     * For the purposes of an SSLSession, we want a way to represent the supplied hostname
+     * or the IP address in a textual representation. We do not want to perform reverse DNS
+     * lookups on this address.
+     */
+    String getHostnameOrIP() {
+        if (peerHostname != null) {
+            return peerHostname;
+        }
+
+        InetAddress peerAddress = getInetAddress();
+        if (peerAddress != null) {
+            return peerAddress.getHostAddress();
+        }
+
+        return null;
+    }
+
+    /**
+     * Note write timeouts are not part of the javax.net.ssl.SSLSocket API
+     */
+    void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException {
+        throw new SocketException("Method setSoWriteTimeout() is not supported.");
+    }
+
+    /**
+     * Note write timeouts are not part of the javax.net.ssl.SSLSocket API
+     */
+    int getSoWriteTimeout() throws SocketException {
+        return 0;
+    }
+
+    /**
+     * Set the handshake timeout on this socket.  This timeout is specified in
+     * milliseconds and will be used only during the handshake process.
+     */
+    void setHandshakeTimeout(int handshakeTimeoutMilliseconds) throws SocketException {
+        throw new SocketException("Method setHandshakeTimeout() is not supported.");
+    }
+
+    /**
+     * This method enables session ticket support.
+     *
+     * @param useSessionTickets True to enable session tickets
+     */
+    abstract void setUseSessionTickets(boolean useSessionTickets);
+
+    /**
+     * Enables/disables TLS Channel ID for this server socket.
+     *
+     * <p>This method needs to be invoked before the handshake starts.
+     *
+     * @throws IllegalStateException if this is a client socket or if the handshake has already
+     *         started.
+     */
+    abstract void setChannelIdEnabled(boolean enabled);
+
+    /**
+     * Gets the TLS Channel ID for this server socket. Channel ID is only available once the
+     * handshake completes.
+     *
+     * @return channel ID or {@code null} if not available.
+     *
+     * @throws IllegalStateException if this is a client socket or if the handshake has not yet
+     *         completed.
+     * @throws SSLException if channel ID is available but could not be obtained.
+     */
+    abstract byte[] getChannelId() throws SSLException;
+
+    /**
+     * Sets the {@link PrivateKey} to be used for TLS Channel ID by this client socket.
+     *
+     * <p>This method needs to be invoked before the handshake starts.
+     *
+     * @param privateKey private key (enables TLS Channel ID) or {@code null} for no key (disables
+     *        TLS Channel ID). The private key must be an Elliptic Curve (EC) key based on the NIST
+     *        P-256 curve (aka SECG secp256r1 or ANSI X9.62 prime256v1).
+     *
+     * @throws IllegalStateException if this is a server socket or if the handshake has already
+     *         started.
+     */
+    abstract void setChannelIdPrivateKey(PrivateKey privateKey);
+
+    /**
+     * Returns null always for backward compatibility.
+     */
+    byte[] getNpnSelectedProtocol() {
+        return null;
+    }
+
+    /**
+     * This method does nothing and is kept for backward compatibility.
+     */
+    void setNpnProtocols(byte[] npnProtocols) {}
+
+    /**
+     * Returns the protocol agreed upon by client and server, or {@code null} if
+     * no protocol was agreed upon.
+     */
+    abstract byte[] getAlpnSelectedProtocol();
+
+    /**
+     * Sets the list of ALPN protocols. This method internally converts the protocols to their
+     * wire-format form.
+     *
+     * @param alpnProtocols the list of ALPN protocols
+     * @see #setAlpnProtocols(byte[])
+     */
+    abstract void setAlpnProtocols(String[] alpnProtocols);
+
+    /**
+     * Alternate version of {@link #setAlpnProtocols(String[])} that directly sets the list of
+     * ALPN in the wire-format form used by BoringSSL (length-prefixed 8-bit strings).
+     * Requires that all strings be encoded with US-ASCII.
+     *
+     * @param alpnProtocols the encoded form of the ALPN protocol list
+     * @see #setAlpnProtocols(String[])
+     */
+    abstract void setAlpnProtocols(byte[] alpnProtocols);
+
+    /**
+     * Called by {@link #notifyHandshakeCompletedListeners()} to get the currently active session.
+     * Unlike {@link #getSession()}, this method must not block.
+     */
+    abstract SSLSession getActiveSession();
+
+    final PeerInfoProvider peerInfoProvider() {
+        return peerInfoProvider;
+    }
+
+    final void checkOpen() throws SocketException {
+        if (isClosed()) {
+            throw new SocketException("Socket is closed");
+        }
+    }
+
+    final void notifyHandshakeCompletedListeners() {
+        if (listeners != null && !listeners.isEmpty()) {
+            // notify the listeners
+            HandshakeCompletedEvent event = new HandshakeCompletedEvent(this, getActiveSession());
+            for (HandshakeCompletedListener listener : listeners) {
+                try {
+                    listener.handshakeCompleted(event);
+                } catch (RuntimeException e) {
+                    // The RI runs the handlers in a separate thread,
+                    // which we do not. But we try to preserve their
+                    // behavior of logging a problem and not killing
+                    // the handshaking thread just because a listener
+                    // has a problem.
+                    Thread thread = Thread.currentThread();
+                    thread.getUncaughtExceptionHandler().uncaughtException(thread, e);
+                }
+            }
+        }
+    }
+
+    private boolean isDelegating() {
+        // Checking for null to handle the case of calling virtual methods in the super class
+        // constructor.
+        return socket != null && socket != this;
+    }
+}
diff --git a/common/src/main/java/org/conscrypt/Conscrypt.java b/common/src/main/java/org/conscrypt/Conscrypt.java
index 14fe6bd..10d23d1 100644
--- a/common/src/main/java/org/conscrypt/Conscrypt.java
+++ b/common/src/main/java/org/conscrypt/Conscrypt.java
@@ -15,9 +15,7 @@
  */
 package org.conscrypt;
 
-import java.io.FileDescriptor;
 import java.io.UnsupportedEncodingException;
-import java.net.SocketException;
 import java.nio.ByteBuffer;
 import java.security.KeyManagementException;
 import java.security.PrivateKey;
@@ -168,25 +166,15 @@
          * Indicates whether the given socket is a Conscrypt socket.
          */
         public static boolean isConscrypt(SSLSocket socket) {
-            return socket instanceof OpenSSLSocketImpl;
+            return socket instanceof AbstractConscryptSocket;
         }
 
-        private static OpenSSLSocketImpl toConscrypt(SSLSocket socket) {
+        private static AbstractConscryptSocket toConscrypt(SSLSocket socket) {
             if (!isConscrypt(socket)) {
                 throw new IllegalArgumentException(
                         "Not a conscrypt socket: " + socket.getClass().getName());
             }
-            return (OpenSSLSocketImpl) socket;
-        }
-
-        /**
-         * This method enables session ticket support.
-         *
-         * @param socket the socket
-         * @param useSessionTickets True to enable session tickets
-         */
-        public static void setUseSessionTickets(SSLSocket socket, boolean useSessionTickets) {
-            toConscrypt(socket).setUseSessionTickets(useSessionTickets);
+            return (AbstractConscryptSocket) socket;
         }
 
         /**
@@ -218,34 +206,13 @@
         }
 
         /**
-         * Note write timeouts are not part of the javax.net.ssl.SSLSocket API
+         * This method enables session ticket support.
+         *
+         * @param socket the socket
+         * @param useSessionTickets True to enable session tickets
          */
-        public static void setSoWriteTimeout(SSLSocket socket, int writeTimeoutMilliseconds)
-                throws SocketException {
-            toConscrypt(socket).setSoWriteTimeout(writeTimeoutMilliseconds);
-        }
-
-        /**
-         * Note write timeouts are not part of the javax.net.ssl.SSLSocket API
-         */
-        public static int getSoWriteTimeout(SSLSocket socket) throws SocketException {
-            return toConscrypt(socket).getSoWriteTimeout();
-        }
-
-        /**
-         * Set the handshake timeout on this socket.  This timeout is specified in
-         * milliseconds and will be used only during the handshake process.
-         */
-        public static void setHandshakeTimeout(SSLSocket socket, int handshakeTimeoutMilliseconds)
-                throws SocketException {
-            toConscrypt(socket).setHandshakeTimeout(handshakeTimeoutMilliseconds);
-        }
-
-        /**
-         * Gets the underlying file descriptor for the given socket.
-         */
-        public static FileDescriptor getFileDescriptor(SSLSocket socket) {
-            return toConscrypt(socket).getFileDescriptor$();
+        public static void setUseSessionTickets(SSLSocket socket, boolean useSessionTickets) {
+            toConscrypt(socket).setUseSessionTickets(useSessionTickets);
         }
 
         /**
diff --git a/common/src/main/java/org/conscrypt/ConscryptEngine.java b/common/src/main/java/org/conscrypt/ConscryptEngine.java
index d62323b..d7b6d12 100644
--- a/common/src/main/java/org/conscrypt/ConscryptEngine.java
+++ b/common/src/main/java/org/conscrypt/ConscryptEngine.java
@@ -301,8 +301,8 @@
     }
 
     /**
-     * This method enables Server Name Indication (SNI) and overrides the hostname supplied
-     * during engine creation.
+     * This method enables Server Name Indication (SNI) and overrides the {@link PeerInfoProvider}
+     * supplied during engine creation.
      */
     void setHostname(String hostname) {
         sslParameters.setUseSni(hostname != null);
@@ -310,12 +310,12 @@
     }
 
     /**
-     * Returns either the hostname supplied during engine creation or via
-     * {@link #setHostname(String)}. No DNS resolution is attempted before
+     * Returns the hostname from {@link #setHostname(String)} or supplied by the
+     * {@link PeerInfoProvider} upon creation. No DNS resolution is attempted before
      * returning the hostname.
      */
     String getHostname() {
-        return peerHostname;
+        return peerHostname != null ? peerHostname : peerInfoProvider.getHostname();
     }
 
     @Override
diff --git a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
new file mode 100644
index 0000000..433a6bc
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
@@ -0,0 +1,763 @@
+/*
+ * Copyright 2016 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;
+
+import static javax.net.ssl.SSLEngineResult.Status.OK;
+import static org.conscrypt.SSLUtils.EngineStates.STATE_CLOSED;
+import static org.conscrypt.SSLUtils.EngineStates.STATE_HANDSHAKE_COMPLETED;
+import static org.conscrypt.SSLUtils.EngineStates.STATE_HANDSHAKE_STARTED;
+import static org.conscrypt.SSLUtils.EngineStates.STATE_NEW;
+import static org.conscrypt.SSLUtils.EngineStates.STATE_READY;
+import static org.conscrypt.SSLUtils.EngineStates.STATE_READY_HANDSHAKE_CUT_THROUGH;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.security.PrivateKey;
+import javax.crypto.SecretKey;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.X509KeyManager;
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * Implements crypto handling by delegating to {@link ConscryptEngine}.
+ */
+final class ConscryptEngineSocket extends OpenSSLSocketImpl
+        implements SSLParametersImpl.AliasChooser, SSLParametersImpl.PSKCallbacks {
+    private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
+
+    private final ConscryptEngine engine;
+    private final Object stateLock = new Object();
+    private final Object handshakeLock = new Object();
+
+    private SSLOutputStream out;
+    private SSLInputStream in;
+
+    // @GuardedBy("stateLock");
+    private int state = STATE_NEW;
+
+    ConscryptEngineSocket(SSLParametersImpl sslParameters) throws IOException {
+        engine = newEngine(sslParameters, this);
+    }
+
+    ConscryptEngineSocket(String hostname, int port, SSLParametersImpl sslParameters)
+            throws IOException {
+        super(hostname, port);
+        engine = newEngine(sslParameters, this);
+    }
+
+    ConscryptEngineSocket(InetAddress address, int port, SSLParametersImpl sslParameters)
+            throws IOException {
+        super(address, port);
+        engine = newEngine(sslParameters, this);
+    }
+
+    ConscryptEngineSocket(String hostname, int port, InetAddress clientAddress, int clientPort,
+            SSLParametersImpl sslParameters) throws IOException {
+        super(hostname, port, clientAddress, clientPort);
+        engine = newEngine(sslParameters, this);
+    }
+
+    ConscryptEngineSocket(InetAddress address, int port, InetAddress clientAddress, int clientPort,
+            SSLParametersImpl sslParameters) throws IOException {
+        super(address, port, clientAddress, clientPort);
+        engine = newEngine(sslParameters, this);
+    }
+
+    ConscryptEngineSocket(Socket socket, String hostname, int port, boolean autoClose,
+            SSLParametersImpl sslParameters) throws IOException {
+        super(socket, hostname, port, autoClose);
+        engine = newEngine(sslParameters, this);
+    }
+
+    private static ConscryptEngine newEngine(
+            SSLParametersImpl sslParameters, final ConscryptEngineSocket socket) {
+        ConscryptEngine engine = new ConscryptEngine(sslParameters, socket.peerInfoProvider());
+
+        // When the handshake completes, notify any listeners.
+        engine.setHandshakeListener(new HandshakeListener() {
+            /**
+             * Protected by {@code stateLock}
+             */
+            @Override
+            public void onHandshakeFinished() {
+                // Just call the outer class method.
+                socket.onHandshakeFinished();
+            }
+        });
+
+        // Transition the engine state to MODE_SET
+        engine.setUseClientMode(sslParameters.getUseClientMode());
+        return engine;
+    }
+
+    @Override
+    public SSLParameters getSSLParameters() {
+        return engine.getSSLParameters();
+    }
+
+    @Override
+    public void setSSLParameters(SSLParameters sslParameters) {
+        engine.setSSLParameters(sslParameters);
+    }
+
+    @Override
+    public void startHandshake() throws IOException {
+        checkOpen();
+
+        if (isHandshakeFinished()) {
+            // TODO(nmittler): Handle renegotiation.
+            return;
+        }
+
+        try {
+            synchronized (handshakeLock) {
+                // Only lock stateLock when we begin the handshake. This is done so that we don't
+                // hold the stateLock when we invoke the handshake completion listeners.
+                synchronized (stateLock) {
+                    // Initialize the handshake if we haven't already.
+                    if (state == STATE_NEW) {
+                        state = STATE_HANDSHAKE_STARTED;
+                        engine.beginHandshake();
+                        in = new SSLInputStream();
+                        out = new SSLOutputStream();
+                    } else {
+                        // We've either started the handshake already or have been closed.
+                        // Do nothing in both cases.
+                        return;
+                    }
+                }
+
+                boolean finished = false;
+                while (!finished) {
+                    switch (engine.getHandshakeStatus()) {
+                        case NEED_UNWRAP:
+                            if (in.readInternal(EmptyArray.BYTE, 0, 0) < 0) {
+                                // Can't complete the handshake due to EOF.
+                                throw SSLUtils.toSSLHandshakeException(new EOFException());
+                            }
+                            break;
+                        case NEED_WRAP: {
+                            out.writeInternal(EMPTY_BUFFER);
+                            // Always flush handshake frames immediately.
+                            out.flushInternal();
+                            break;
+                        }
+                        case NEED_TASK:
+                            // Should never get here, since our engine never provides tasks.
+                            throw new IllegalStateException("Engine tasks are unsupported");
+                        case NOT_HANDSHAKING:
+                        case FINISHED:
+                            // Handshake is complete.
+                            finished = true;
+                            break;
+                        default: {
+                            throw new IllegalStateException(
+                                    "Unknown handshake status: " + engine.getHandshakeStatus());
+                        }
+                    }
+                }
+            }
+        } catch (SSLException e) {
+            close();
+            throw e;
+        } catch (IOException e) {
+            close();
+            throw e;
+        } catch (Exception e) {
+            close();
+            // Convert anything else to a handshake exception.
+            throw SSLUtils.toSSLHandshakeException(e);
+        }
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+        checkOpen();
+
+        // Block waiting for a handshake without a lock held. It's possible that the socket
+        // is closed at this point. If that happens, we'll still return the input stream but
+        // all reads on it will throw.
+        waitForHandshake();
+        return in;
+    }
+
+    @Override
+    public OutputStream getOutputStream() throws IOException {
+        checkOpen();
+
+        // Block waiting for a handshake without a lock held. It's possible that the socket
+        // is closed at this point. If that happens, we'll still return the input stream but
+        // all reads on it will throw.
+        waitForHandshake();
+
+        return out;
+    }
+
+    @Override
+    public SSLSession getHandshakeSession() {
+        return engine.handshakeSession();
+    }
+
+    @Override
+    public SSLSession getSession() {
+        SSLSession session = engine.getSession();
+        if (session == null) {
+            boolean handshakeCompleted = false;
+            try {
+                if (isConnected()) {
+                    waitForHandshake();
+                    handshakeCompleted = true;
+                }
+            } catch (IOException e) {
+                // Fall through.
+            }
+
+            if (!handshakeCompleted) {
+                // return an invalid session with
+                // invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
+                return SSLNullSession.getNullSession();
+            }
+            session = engine.getSession();
+        }
+        return session;
+    }
+
+    @Override
+    SSLSession getActiveSession() {
+        return engine.getSession();
+    }
+
+    @Override
+    public boolean getEnableSessionCreation() {
+        return engine.getEnableSessionCreation();
+    }
+
+    @Override
+    public void setEnableSessionCreation(boolean flag) {
+        engine.setEnableSessionCreation(flag);
+    }
+
+    @Override
+    public String[] getSupportedCipherSuites() {
+        return engine.getSupportedCipherSuites();
+    }
+
+    @Override
+    public String[] getEnabledCipherSuites() {
+        return engine.getEnabledCipherSuites();
+    }
+
+    @Override
+    public void setEnabledCipherSuites(String[] suites) {
+        engine.setEnabledCipherSuites(suites);
+    }
+
+    @Override
+    public String[] getSupportedProtocols() {
+        return engine.getSupportedProtocols();
+    }
+
+    @Override
+    public String[] getEnabledProtocols() {
+        return engine.getEnabledProtocols();
+    }
+
+    @Override
+    public void setEnabledProtocols(String[] protocols) {
+        engine.setEnabledProtocols(protocols);
+    }
+
+    /**
+     * This method enables Server Name Indication
+     *
+     * @param hostname the desired SNI hostname, or null to disable
+     */
+    @Override
+    public void setHostname(String hostname) {
+        engine.setHostname(hostname);
+        super.setHostname(hostname);
+    }
+
+    @Override
+    public void setUseSessionTickets(boolean useSessionTickets) {
+        engine.setUseSessionTickets(useSessionTickets);
+    }
+
+    @Override
+    public void setChannelIdEnabled(boolean enabled) {
+        engine.setChannelIdEnabled(enabled);
+    }
+
+    @Override
+    public byte[] getChannelId() throws SSLException {
+        return engine.getChannelId();
+    }
+
+    @Override
+    public void setChannelIdPrivateKey(PrivateKey privateKey) {
+        engine.setChannelIdPrivateKey(privateKey);
+    }
+
+    @Override
+    public boolean getUseClientMode() {
+        return engine.getUseClientMode();
+    }
+
+    @Override
+    public void setUseClientMode(boolean mode) {
+        engine.setUseClientMode(mode);
+    }
+
+    @Override
+    public boolean getWantClientAuth() {
+        return engine.getWantClientAuth();
+    }
+
+    @Override
+    public boolean getNeedClientAuth() {
+        return engine.getNeedClientAuth();
+    }
+
+    @Override
+    public void setNeedClientAuth(boolean need) {
+        engine.setNeedClientAuth(need);
+    }
+
+    @Override
+    public void setWantClientAuth(boolean want) {
+        engine.setWantClientAuth(want);
+    }
+
+    @Override
+    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
+    public void close() throws IOException {
+        // TODO: Close SSL sockets using a background thread so they close gracefully.
+
+        synchronized (stateLock) {
+            if (state == STATE_CLOSED) {
+                // close() has already been called, so do nothing and return.
+                return;
+            }
+
+            state = STATE_CLOSED;
+
+            stateLock.notifyAll();
+        }
+
+        // Close the underlying socket.
+        super.close();
+
+        // Close the engine.
+        engine.closeInbound();
+        engine.closeOutbound();
+    }
+
+    @Override
+    public byte[] getAlpnSelectedProtocol() {
+        return engine.getAlpnSelectedProtocol();
+    }
+
+    @Override
+    public void setAlpnProtocols(byte[] alpnProtocols) {
+        engine.setAlpnProtocols(alpnProtocols);
+    }
+
+    @Override
+    public void setAlpnProtocols(String[] alpnProtocols) {
+        engine.setAlpnProtocols(alpnProtocols);
+    }
+
+    @Override
+    public String chooseServerAlias(X509KeyManager keyManager, String keyType) {
+        return engine.chooseServerAlias(keyManager, keyType);
+    }
+
+    @Override
+    public String chooseClientAlias(
+            X509KeyManager keyManager, X500Principal[] issuers, String[] keyTypes) {
+        return engine.chooseClientAlias(keyManager, issuers, keyTypes);
+    }
+
+    @Override
+    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
+    public String chooseServerPSKIdentityHint(PSKKeyManager keyManager) {
+        return engine.chooseServerPSKIdentityHint(keyManager);
+    }
+
+    @Override
+    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
+    public String chooseClientPSKIdentity(PSKKeyManager keyManager, String identityHint) {
+        return engine.chooseClientPSKIdentity(keyManager, identityHint);
+    }
+
+    @Override
+    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
+    public SecretKey getPSKKey(PSKKeyManager keyManager, String identityHint, String identity) {
+        return engine.getPSKKey(keyManager, identityHint, identity);
+    }
+
+    private boolean isHandshakeFinished() {
+        return state >= STATE_READY_HANDSHAKE_CUT_THROUGH;
+    }
+
+    private void onHandshakeFinished() {
+        boolean notify = false;
+        synchronized (stateLock) {
+            if (state != STATE_CLOSED) {
+                if (state == STATE_HANDSHAKE_STARTED) {
+                    state = STATE_READY_HANDSHAKE_CUT_THROUGH;
+                } else if (state == STATE_HANDSHAKE_COMPLETED) {
+                    state = STATE_READY;
+                }
+
+                // Unblock threads that are waiting for our state to transition
+                // into STATE_READY or STATE_READY_HANDSHAKE_CUT_THROUGH.
+                stateLock.notifyAll();
+                notify = true;
+            }
+        }
+
+        if (notify) {
+            notifyHandshakeCompletedListeners();
+        }
+    }
+
+    /**
+     * Waits for the handshake to complete.
+     */
+    private void waitForHandshake() throws IOException {
+        startHandshake();
+
+        synchronized (stateLock) {
+            while (state != STATE_READY && state != STATE_READY_HANDSHAKE_CUT_THROUGH
+                    && state != STATE_CLOSED) {
+                try {
+                    stateLock.wait();
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    throw new IOException("Interrupted waiting for handshake", e);
+                }
+            }
+
+            if (state == STATE_CLOSED) {
+                throw new SocketException("Socket is closed");
+            }
+        }
+    }
+
+    private OutputStream getUnderlyingOutputStream() throws IOException {
+        return super.getOutputStream();
+    }
+
+    private InputStream getUnderlyingInputStream() throws IOException {
+        return super.getInputStream();
+    }
+
+    private SocketChannel getUnderlyingChannel() throws IOException {
+        return super.getChannel();
+    }
+
+    /**
+     * Wrap bytes written to the underlying socket.
+     */
+    private final class SSLOutputStream extends OutputStream {
+        private final Object writeLock = new Object();
+        private ByteBuffer target;
+        private OutputStream socketOutputStream;
+        private SocketChannel socketChannel;
+
+        SSLOutputStream() {}
+
+        @Override
+        public void close() throws IOException {
+            ConscryptEngineSocket.this.close();
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            startHandshake();
+            synchronized (writeLock) {
+                write(new byte[] {(byte) b});
+            }
+        }
+
+        @Override
+        public void write(byte[] b) throws IOException {
+            startHandshake();
+            synchronized (writeLock) {
+                writeInternal(ByteBuffer.wrap(b));
+            }
+        }
+
+        @Override
+        public void write(byte[] b, int off, int len) throws IOException {
+            startHandshake();
+            synchronized (writeLock) {
+                writeInternal(ByteBuffer.wrap(b, off, len));
+            }
+        }
+
+        private void writeInternal(ByteBuffer buffer) throws IOException {
+            Platform.blockGuardOnNetwork();
+            checkOpen();
+            init();
+
+            // Need to loop through at least once to enable handshaking where no application
+            // bytes are
+            // processed.
+            int len = buffer.remaining();
+            SSLEngineResult engineResult;
+            do {
+                target.clear();
+                engineResult = engine.wrap(buffer, target);
+                if (engineResult.getStatus() != OK) {
+                    throw new SSLException("Unexpected engine result " + engineResult.getStatus());
+                }
+                if (target.position() != engineResult.bytesProduced()) {
+                    throw new SSLException("Engine bytesProduced " + engineResult.bytesProduced()
+                            + " does not match bytes written " + target.position());
+                }
+                len -= engineResult.bytesConsumed();
+                if (len != buffer.remaining()) {
+                    throw new SSLException("Engine did not read the correct number of bytes");
+                }
+
+                target.flip();
+
+                // Write the data to the socket.
+                writeToSocket();
+            } while (len > 0);
+        }
+
+        @Override
+        public void flush() throws IOException {
+            startHandshake();
+            synchronized (writeLock) {
+                flushInternal();
+            }
+        }
+
+        private void flushInternal() throws IOException {
+            checkOpen();
+            init();
+            socketOutputStream.flush();
+        }
+
+        private void init() throws IOException {
+            if (socketOutputStream == null) {
+                socketOutputStream = getUnderlyingOutputStream();
+                socketChannel = getUnderlyingChannel();
+                if (socketChannel != null) {
+                    // Optimization. Using direct buffers wherever possible to avoid passing
+                    // arrays to JNI.
+                    target = ByteBuffer.allocateDirect(engine.getSession().getPacketBufferSize());
+                } else {
+                    target = ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
+                }
+            }
+        }
+
+        private void writeToSocket() throws IOException {
+            // Write the data to the socket.
+            if (socketChannel != null) {
+                // Loop until all of the data is written to the channel. Typically,
+                // SocketChannel writes will return only after all bytes are written,
+                // so we won't really loop here.
+                while (target.hasRemaining()) {
+                    socketChannel.write(target);
+                }
+            } else {
+                // Target is a heap buffer.
+                socketOutputStream.write(target.array(), 0, target.limit());
+            }
+        }
+    }
+
+    /**
+     * Unwrap bytes read from the underlying socket.
+     */
+    private final class SSLInputStream extends InputStream {
+        private final Object readLock = new Object();
+        private final byte[] singleByte = new byte[1];
+        private final ByteBuffer fromEngine;
+        private ByteBuffer fromSocket;
+        private InputStream socketInputStream;
+        private SocketChannel socketChannel;
+
+        SSLInputStream() {
+            fromEngine = ByteBuffer.allocateDirect(engine.getSession().getApplicationBufferSize());
+            // Initially fromEngine.remaining() == 0.
+            fromEngine.flip();
+        }
+
+        @Override
+        public void close() throws IOException {
+            ConscryptEngineSocket.this.close();
+        }
+
+        @Override
+        public int read() throws IOException {
+            startHandshake();
+            synchronized (readLock) {
+                // Handle returning of -1 if EOF is reached.
+                int count = read(singleByte, 0, 1);
+                if (count == -1) {
+                    // Handle EOF.
+                    return -1;
+                }
+                if (count != 1) {
+                    throw new SSLException("read incorrect number of bytes " + count);
+                }
+                return (int) singleByte[0];
+            }
+        }
+
+        @Override
+        public int read(byte[] b) throws IOException {
+            startHandshake();
+            synchronized (readLock) {
+                return read(b, 0, b.length);
+            }
+        }
+
+        @Override
+        public int read(byte[] b, int off, int len) throws IOException {
+            startHandshake();
+            synchronized (readLock) {
+                return readInternal(b, off, len);
+            }
+        }
+
+        @Override
+        public int available() throws IOException {
+            startHandshake();
+            synchronized (readLock) {
+                init();
+                return fromEngine.remaining()
+                        + (fromSocket.hasRemaining() || socketInputStream.available() > 0 ? 1 : 0);
+            }
+        }
+
+        private int readInternal(byte[] b, int off, int len) throws IOException {
+            Platform.blockGuardOnNetwork();
+            checkOpen();
+
+            // Make sure the input stream has been created.
+            init();
+
+            for (;;) {
+                // Serve any remaining data from the engine first.
+                if (fromEngine.remaining() > 0) {
+                    int readFromEngine = Math.min(fromEngine.remaining(), len);
+                    fromEngine.get(b, off, readFromEngine);
+                    return readFromEngine;
+                }
+
+                // Try to unwrap any data already in the socket buffer.
+                boolean needMoreDataFromSocket = true;
+
+                // Unwrap the unencrypted bytes into the engine buffer.
+                fromSocket.flip();
+                fromEngine.clear();
+                SSLEngineResult engineResult = engine.unwrap(fromSocket, fromEngine);
+                // Shift any remaining data to the beginning of the buffer so that
+                // we can accommodate the next full packet. After this is called,
+                // limit will be restored to capacity and position will point just
+                // past the end of the data.
+                fromSocket.compact();
+                fromEngine.flip();
+
+                switch (engineResult.getStatus()) {
+                    case BUFFER_UNDERFLOW: {
+                        if (engineResult.bytesProduced() == 0) {
+                            // Need to read more data from the socket.
+                            break;
+                        }
+                        // Also serve the data that was produced.
+                        needMoreDataFromSocket = false;
+                        break;
+                    }
+                    case OK: {
+                        // We processed the entire packet successfully.
+                        needMoreDataFromSocket = false;
+                        break;
+                    }
+                    case CLOSED: {
+                        // EOF
+                        return -1;
+                    }
+                    default: {
+                        // Anything else is an error.
+                        throw new SSLException(
+                                "Unexpected engine result " + engineResult.getStatus());
+                    }
+                }
+
+                if (!needMoreDataFromSocket && engineResult.bytesProduced() == 0) {
+                    // Read successfully, but produced no data. Possibly part of a
+                    // handshake.
+                    return 0;
+                }
+
+                // Read more data from the socket.
+                if (needMoreDataFromSocket && readFromSocket() == -1) {
+                    // Failed to read the next encrypted packet before reaching EOF.
+                    return -1;
+                }
+
+                // Continue the loop and return the data from the engine buffer.
+            }
+        }
+
+        private void init() throws IOException {
+            if (socketInputStream == null) {
+                socketInputStream = getUnderlyingInputStream();
+                socketChannel = getUnderlyingChannel();
+                if (socketChannel != null) {
+                    fromSocket =
+                            ByteBuffer.allocateDirect(engine.getSession().getPacketBufferSize());
+                } else {
+                    fromSocket = ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
+                }
+            }
+        }
+
+        private int readFromSocket() throws IOException {
+            if (socketChannel != null) {
+                return socketChannel.read(fromSocket);
+            }
+            // Read directly to the underlying array and increment the buffer position if
+            // appropriate.
+            int read = socketInputStream.read(
+                    fromSocket.array(), fromSocket.position(), fromSocket.remaining());
+            if (read > 0) {
+                fromSocket.position(fromSocket.position() + read);
+            }
+            return read;
+        }
+    }
+}
diff --git a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
new file mode 100644
index 0000000..1fb9ee8
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
@@ -0,0 +1,1092 @@
+/*
+ * Copyright (C) 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;
+
+import static org.conscrypt.SSLUtils.EngineStates.STATE_CLOSED;
+import static org.conscrypt.SSLUtils.EngineStates.STATE_HANDSHAKE_COMPLETED;
+import static org.conscrypt.SSLUtils.EngineStates.STATE_HANDSHAKE_STARTED;
+import static org.conscrypt.SSLUtils.EngineStates.STATE_NEW;
+import static org.conscrypt.SSLUtils.EngineStates.STATE_READY;
+import static org.conscrypt.SSLUtils.EngineStates.STATE_READY_HANDSHAKE_CUT_THROUGH;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.interfaces.ECKey;
+import java.security.spec.ECParameterSpec;
+import javax.crypto.SecretKey;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLProtocolException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * Implementation of the class OpenSSLSocketImpl based on OpenSSL.
+ * <p>
+ * Extensions to SSLSocket include:
+ * <ul>
+ * <li>handshake timeout
+ * <li>session tickets
+ * <li>Server Name Indication
+ * </ul>
+ */
+final class ConscryptFileDescriptorSocket extends OpenSSLSocketImpl
+        implements NativeCrypto.SSLHandshakeCallbacks, SSLParametersImpl.AliasChooser,
+                   SSLParametersImpl.PSKCallbacks {
+    private static final boolean DBG_STATE = false;
+
+    /**
+     * Protects handshakeStarted and handshakeCompleted.
+     */
+    private final Object stateLock = new Object();
+
+    // @GuardedBy("stateLock");
+    private int state = STATE_NEW;
+
+    /**
+     * Protected by synchronizing on stateLock. Starts as 0, set by
+     * startHandshake, reset to 0 on close.
+     */
+    // @GuardedBy("stateLock");
+    private long sslNativePointer;
+
+    /**
+     * Protected by synchronizing on stateLock. Starts as null, set by
+     * getInputStream.
+     */
+    // @GuardedBy("stateLock");
+    private SSLInputStream is;
+
+    /**
+     * Protected by synchronizing on stateLock. Starts as null, set by
+     * getInputStream.
+     */
+    // @GuardedBy("stateLock");
+    private SSLOutputStream os;
+
+    private final SSLParametersImpl sslParameters;
+
+    /*
+     * A CloseGuard object on Android. On other platforms, this is nothing.
+     */
+    private final Object guard = Platform.closeGuardGet();
+
+    /**
+     * Private key for the TLS Channel ID extension. This field is client-side
+     * only. Set during startHandshake.
+     */
+    private OpenSSLKey channelIdPrivateKey;
+
+    /** Set during startHandshake. */
+    private AbstractOpenSSLSession sslSession;
+
+    /** Used during handshake callbacks. */
+    private AbstractOpenSSLSession handshakeSession;
+
+    private int writeTimeoutMilliseconds = 0;
+    private int handshakeTimeoutMilliseconds = -1; // -1 = same as timeout; 0 = infinite
+
+    ConscryptFileDescriptorSocket(SSLParametersImpl sslParameters) throws IOException {
+        this.sslParameters = sslParameters;
+    }
+
+    ConscryptFileDescriptorSocket(String hostname, int port, SSLParametersImpl sslParameters)
+            throws IOException {
+        super(hostname, port);
+        this.sslParameters = sslParameters;
+    }
+
+    ConscryptFileDescriptorSocket(InetAddress address, int port, SSLParametersImpl sslParameters)
+            throws IOException {
+        super(address, port);
+        this.sslParameters = sslParameters;
+    }
+
+    ConscryptFileDescriptorSocket(String hostname, int port, InetAddress clientAddress,
+            int clientPort, SSLParametersImpl sslParameters) throws IOException {
+        super(hostname, port, clientAddress, clientPort);
+        this.sslParameters = sslParameters;
+    }
+
+    ConscryptFileDescriptorSocket(InetAddress address, int port, InetAddress clientAddress,
+            int clientPort, SSLParametersImpl sslParameters) throws IOException {
+        super(address, port, clientAddress, clientPort);
+        this.sslParameters = sslParameters;
+    }
+
+    ConscryptFileDescriptorSocket(Socket socket, String hostname, int port, boolean autoClose,
+            SSLParametersImpl sslParameters) throws IOException {
+        super(socket, hostname, port, autoClose);
+        this.sslParameters = sslParameters;
+    }
+
+    /**
+     * Starts a TLS/SSL handshake on this connection using some native methods
+     * from the OpenSSL library. It can negotiate new encryption keys, change
+     * cipher suites, or initiate a new session. The certificate chain is
+     * verified if the correspondent property in java.Security is set. All
+     * listeners are notified at the end of the TLS/SSL handshake.
+     */
+    @Override
+    public void startHandshake() throws IOException {
+        checkOpen();
+        synchronized (stateLock) {
+            if (state == STATE_NEW) {
+                state = STATE_HANDSHAKE_STARTED;
+            } else {
+                // We've either started the handshake already or have been closed.
+                // Do nothing in both cases.
+                return;
+            }
+        }
+
+        final boolean client = sslParameters.getUseClientMode();
+
+        sslNativePointer = 0;
+        boolean releaseResources = true;
+        try {
+            final AbstractSessionContext sessionContext = sslParameters.getSessionContext();
+            sslNativePointer = NativeCrypto.SSL_new(sessionContext.sslCtxNativePointer);
+            Platform.closeGuardOpen(guard, "close");
+
+            boolean enableSessionCreation = getEnableSessionCreation();
+            if (!enableSessionCreation) {
+                NativeCrypto.SSL_set_session_creation_enabled(sslNativePointer,
+                        enableSessionCreation);
+            }
+
+            // Allow servers to trigger renegotiation. Some inadvisable server
+            // configurations cause them to attempt to renegotiate during
+            // certain protocols.
+            NativeCrypto.SSL_accept_renegotiations(sslNativePointer);
+
+            if (client) {
+                NativeCrypto.SSL_set_connect_state(sslNativePointer);
+
+                // Configure OCSP and CT extensions for client
+                NativeCrypto.SSL_enable_ocsp_stapling(sslNativePointer);
+                if (sslParameters.isCTVerificationEnabled(getHostname())) {
+                    NativeCrypto.SSL_enable_signed_cert_timestamps(sslNativePointer);
+                }
+            } else {
+                NativeCrypto.SSL_set_accept_state(sslNativePointer);
+
+                // Configure OCSP for server
+                if (sslParameters.getOCSPResponse() != null) {
+                    NativeCrypto.SSL_enable_ocsp_stapling(sslNativePointer);
+                }
+            }
+
+            final AbstractOpenSSLSession sessionToReuse =
+                    sslParameters.getSessionToReuse(sslNativePointer, getHostnameOrIP(), getPort());
+            sslParameters.setSSLParameters(sslNativePointer, this, this, getHostname());
+            sslParameters.setCertificateValidation(sslNativePointer);
+            sslParameters.setTlsChannelId(sslNativePointer, channelIdPrivateKey);
+
+            // Temporarily use a different timeout for the handshake process
+            int savedReadTimeoutMilliseconds = getSoTimeout();
+            int savedWriteTimeoutMilliseconds = getSoWriteTimeout();
+            if (handshakeTimeoutMilliseconds >= 0) {
+                setSoTimeout(handshakeTimeoutMilliseconds);
+                setSoWriteTimeout(handshakeTimeoutMilliseconds);
+            }
+
+            synchronized (stateLock) {
+                if (state == STATE_CLOSED) {
+                    return;
+                }
+            }
+
+            long sslSessionNativePointer;
+            try {
+                NativeCrypto.SSL_do_handshake(
+                        sslNativePointer, Platform.getFileDescriptor(socket), this, getSoTimeout());
+                sslSessionNativePointer = NativeCrypto.SSL_get1_session(sslNativePointer);
+            } catch (CertificateException e) {
+                SSLHandshakeException wrapper = new SSLHandshakeException(e.getMessage());
+                wrapper.initCause(e);
+                throw wrapper;
+            } catch (SSLException e) {
+                // Swallow this exception if it's thrown as the result of an interruption.
+                //
+                // TODO: SSL_read and SSL_write return -1 when interrupted, but SSL_do_handshake
+                // will throw the last sslError that it saw before sslSelect, usually SSL_WANT_READ
+                // (or WANT_WRITE). Catching that exception here doesn't seem much worse than
+                // changing the native code to return a "special" native pointer value when that
+                // happens.
+                synchronized (stateLock) {
+                    if (state == STATE_CLOSED) {
+                        return;
+                    }
+                }
+
+                // Write CCS errors to EventLog
+                String message = e.getMessage();
+                // Must match error string of SSL_R_UNEXPECTED_CCS
+                if (message.contains("unexpected CCS")) {
+                    String logMessage = String.format("ssl_unexpected_ccs: host=%s",
+                            getHostnameOrIP());
+                    Platform.logEvent(logMessage);
+                }
+
+                throw e;
+            }
+
+            boolean handshakeCompleted = false;
+            synchronized (stateLock) {
+                if (state == STATE_HANDSHAKE_COMPLETED) {
+                    handshakeCompleted = true;
+                } else if (state == STATE_CLOSED) {
+                    return;
+                }
+            }
+
+            sslSession = sslParameters.setupSession(sslSessionNativePointer, sslNativePointer,
+                    sessionToReuse, getHostnameOrIP(), getPort(), handshakeCompleted);
+
+            // Restore the original timeout now that the handshake is complete
+            if (handshakeTimeoutMilliseconds >= 0) {
+                setSoTimeout(savedReadTimeoutMilliseconds);
+                setSoWriteTimeout(savedWriteTimeoutMilliseconds);
+            }
+
+            // if not, notifyHandshakeCompletedListeners later in handshakeCompleted() callback
+            if (handshakeCompleted) {
+                notifyHandshakeCompletedListeners();
+            }
+
+            synchronized (stateLock) {
+                releaseResources = (state == STATE_CLOSED);
+
+                if (state == STATE_HANDSHAKE_STARTED) {
+                    state = STATE_READY_HANDSHAKE_CUT_THROUGH;
+                } else if (state == STATE_HANDSHAKE_COMPLETED) {
+                    state = STATE_READY;
+                }
+
+                if (!releaseResources) {
+                    // Unblock threads that are waiting for our state to transition
+                    // into STATE_READY or STATE_READY_HANDSHAKE_CUT_THROUGH.
+                    stateLock.notifyAll();
+                }
+            }
+        } catch (SSLProtocolException e) {
+            throw (SSLHandshakeException) new SSLHandshakeException("Handshake failed")
+                    .initCause(e);
+        } finally {
+            // on exceptional exit, treat the socket as closed
+            if (releaseResources) {
+                synchronized (stateLock) {
+                    // Mark the socket as closed since we might have reached this as
+                    // a result on an exception thrown by the handshake process.
+                    //
+                    // The state will already be set to closed if we reach this as a result of
+                    // an early return or an interruption due to a concurrent call to close().
+                    state = STATE_CLOSED;
+                    stateLock.notifyAll();
+                }
+
+                try {
+                    shutdownAndFreeSslNative();
+                } catch (IOException ignored) {
+                    // Ignored.
+                }
+            }
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks / client_cert_cb
+    public void clientCertificateRequested(byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals)
+            throws CertificateEncodingException, SSLException {
+        sslParameters.chooseClientCertificate(keyTypeBytes, asn1DerEncodedPrincipals,
+                sslNativePointer, this);
+    }
+
+    @Override
+    @SuppressWarnings("unused") // used by native psk_client_callback
+    public int clientPSKKeyRequested(String identityHint, byte[] identity, byte[] key) {
+        return sslParameters.clientPSKKeyRequested(identityHint, identity, key, this);
+    }
+
+    @Override
+    @SuppressWarnings("unused") // used by native psk_server_callback
+    public int serverPSKKeyRequested(String identityHint, String identity, byte[] key) {
+        return sslParameters.serverPSKKeyRequested(identityHint, identity, key, this);
+    }
+
+    @Override
+    @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks / info_callback
+    public void onSSLStateChange(int type, int val) {
+        if (type != NativeConstants.SSL_CB_HANDSHAKE_DONE) {
+            return;
+        }
+
+        synchronized (stateLock) {
+            if (state == STATE_HANDSHAKE_STARTED) {
+                // If sslSession is null, the handshake was completed during
+                // the call to NativeCrypto.SSL_do_handshake and not during a
+                // later read operation. That means we do not need to fix up
+                // the SSLSession and session cache or notify
+                // HandshakeCompletedListeners, it will be done in
+                // startHandshake.
+
+                state = STATE_HANDSHAKE_COMPLETED;
+                return;
+            } else if (state == STATE_READY_HANDSHAKE_CUT_THROUGH) {
+                // We've returned from startHandshake, which means we've set a sslSession etc.
+                // we need to fix them up, which we'll do outside this lock.
+            } else if (state == STATE_CLOSED) {
+                // Someone called "close" but the handshake hasn't been interrupted yet.
+                return;
+            }
+        }
+
+        // reset session id from the native pointer and update the
+        // appropriate cache.
+        sslSession.resetId();
+        AbstractSessionContext sessionContext =
+            (sslParameters.getUseClientMode())
+            ? sslParameters.getClientSessionContext()
+                : sslParameters.getServerSessionContext();
+        sessionContext.putSession(sslSession);
+
+        // let listeners know we are finally done
+        notifyHandshakeCompletedListeners();
+
+        synchronized (stateLock) {
+            // Now that we've fixed up our state, we can tell waiting threads that
+            // we're ready.
+            state = STATE_READY;
+            // Notify all threads waiting for the handshake to complete.
+            stateLock.notifyAll();
+        }
+    }
+
+    @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks
+    @Override
+    public void verifyCertificateChain(long[] certRefs, String authMethod)
+            throws CertificateException {
+        try {
+            X509TrustManager x509tm = sslParameters.getX509TrustManager();
+            if (x509tm == null) {
+                throw new CertificateException("No X.509 TrustManager");
+            }
+            if (certRefs == null || certRefs.length == 0) {
+                throw new SSLException("Peer sent no certificate");
+            }
+            OpenSSLX509Certificate[] peerCertChain =
+                    OpenSSLX509Certificate.createCertChain(certRefs);
+
+            byte[] ocspData = NativeCrypto.SSL_get_ocsp_response(sslNativePointer);
+            byte[] tlsSctData = NativeCrypto.SSL_get_signed_cert_timestamp_list(sslNativePointer);
+
+            // Used for verifyCertificateChain callback
+            handshakeSession = new OpenSSLSessionImpl(
+                    NativeCrypto.SSL_get1_session(sslNativePointer), null, peerCertChain, ocspData,
+                    tlsSctData, getHostnameOrIP(), getPort(), null);
+
+            boolean client = sslParameters.getUseClientMode();
+            if (client) {
+                Platform.checkServerTrusted(x509tm, peerCertChain, authMethod, this);
+            } else {
+                String authType = peerCertChain[0].getPublicKey().getAlgorithm();
+                Platform.checkClientTrusted(x509tm, peerCertChain, authType, this);
+            }
+        } catch (CertificateException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new CertificateException(e);
+        } finally {
+            // Clear this before notifying handshake completed listeners
+            handshakeSession = null;
+        }
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+        checkOpen();
+
+        InputStream returnVal;
+        synchronized (stateLock) {
+            if (state == STATE_CLOSED) {
+                throw new SocketException("Socket is closed.");
+            }
+
+            if (is == null) {
+                is = new SSLInputStream();
+            }
+
+            returnVal = is;
+        }
+
+        // Block waiting for a handshake without a lock held. It's possible that the socket
+        // is closed at this point. If that happens, we'll still return the input stream but
+        // all reads on it will throw.
+        waitForHandshake();
+        return returnVal;
+    }
+
+    @Override
+    public OutputStream getOutputStream() throws IOException {
+        checkOpen();
+
+        OutputStream returnVal;
+        synchronized (stateLock) {
+            if (state == STATE_CLOSED) {
+                throw new SocketException("Socket is closed.");
+            }
+
+            if (os == null) {
+                os = new SSLOutputStream();
+            }
+
+            returnVal = os;
+        }
+
+        // Block waiting for a handshake without a lock held. It's possible that the socket
+        // is closed at this point. If that happens, we'll still return the output stream but
+        // all writes on it will throw.
+        waitForHandshake();
+        return returnVal;
+    }
+
+    private void assertReadableOrWriteableState() {
+        if (state == STATE_READY || state == STATE_READY_HANDSHAKE_CUT_THROUGH) {
+            return;
+        }
+
+        throw new AssertionError("Invalid state: " + state);
+    }
+
+    private void waitForHandshake() throws IOException {
+        startHandshake();
+
+        synchronized (stateLock) {
+            while (state != STATE_READY &&
+                    state != STATE_READY_HANDSHAKE_CUT_THROUGH &&
+                    state != STATE_CLOSED) {
+                try {
+                    stateLock.wait();
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    throw new IOException("Interrupted waiting for handshake", e);
+                }
+            }
+
+            if (state == STATE_CLOSED) {
+                throw new SocketException("Socket is closed");
+            }
+        }
+    }
+
+    /**
+     * This inner class provides input data stream functionality
+     * for the OpenSSL native implementation. It is used to
+     * read data received via SSL protocol.
+     */
+    private class SSLInputStream extends InputStream {
+        /**
+         * OpenSSL only lets one thread read at a time, so this is used to
+         * make sure we serialize callers of SSL_read. Thread is already
+         * expected to have completed handshaking.
+         */
+        private final Object readLock = new Object();
+
+        SSLInputStream() {
+        }
+
+        /**
+         * Reads one byte. If there is no data in the underlying buffer,
+         * this operation can block until the data will be
+         * available.
+         */
+        @Override
+        public int read() throws IOException {
+            byte[] buffer = new byte[1];
+            int result = read(buffer, 0, 1);
+            return (result != -1) ? buffer[0] & 0xff : -1;
+        }
+
+        /**
+         * Method acts as described in spec for superclass.
+         * @see java.io.InputStream#read(byte[],int,int)
+         */
+        @Override
+        public int read(byte[] buf, int offset, int byteCount) throws IOException {
+            Platform.blockGuardOnNetwork();
+
+            checkOpen();
+            ArrayUtils.checkOffsetAndCount(buf.length, offset, byteCount);
+            if (byteCount == 0) {
+                return 0;
+            }
+
+            synchronized (readLock) {
+                synchronized (stateLock) {
+                    if (state == STATE_CLOSED) {
+                        throw new SocketException("socket is closed");
+                    }
+
+                    if (DBG_STATE) assertReadableOrWriteableState();
+                }
+
+                return NativeCrypto.SSL_read(sslNativePointer, Platform.getFileDescriptor(socket),
+                        ConscryptFileDescriptorSocket.this, buf, offset, byteCount, getSoTimeout());
+            }
+        }
+
+        void awaitPendingOps() {
+            if (DBG_STATE) {
+                synchronized (stateLock) {
+                    if (state != STATE_CLOSED) throw new AssertionError("State is: " + state);
+                }
+            }
+
+            synchronized (readLock) { }
+        }
+    }
+
+    /**
+     * This inner class provides output data stream functionality
+     * for the OpenSSL native implementation. It is used to
+     * write data according to the encryption parameters given in SSL context.
+     */
+    private class SSLOutputStream extends OutputStream {
+        /**
+         * OpenSSL only lets one thread write at a time, so this is used
+         * to make sure we serialize callers of SSL_write. Thread is
+         * already expected to have completed handshaking.
+         */
+        private final Object writeLock = new Object();
+
+        SSLOutputStream() {
+        }
+
+        /**
+         * Method acts as described in spec for superclass.
+         * @see java.io.OutputStream#write(int)
+         */
+        @Override
+        public void write(int oneByte) throws IOException {
+            byte[] buffer = new byte[1];
+            buffer[0] = (byte) (oneByte & 0xff);
+            write(buffer);
+        }
+
+        /**
+         * Method acts as described in spec for superclass.
+         * @see java.io.OutputStream#write(byte[],int,int)
+         */
+        @Override
+        public void write(byte[] buf, int offset, int byteCount) throws IOException {
+            Platform.blockGuardOnNetwork();
+            checkOpen();
+            ArrayUtils.checkOffsetAndCount(buf.length, offset, byteCount);
+            if (byteCount == 0) {
+                return;
+            }
+
+            synchronized (writeLock) {
+                synchronized (stateLock) {
+                    if (state == STATE_CLOSED) {
+                        throw new SocketException("socket is closed");
+                    }
+
+                    if (DBG_STATE) assertReadableOrWriteableState();
+                }
+
+                NativeCrypto.SSL_write(sslNativePointer, Platform.getFileDescriptor(socket),
+                        ConscryptFileDescriptorSocket.this, buf, offset, byteCount,
+                        writeTimeoutMilliseconds);
+            }
+        }
+
+        void awaitPendingOps() {
+            if (DBG_STATE) {
+                synchronized (stateLock) {
+                    if (state != STATE_CLOSED) throw new AssertionError("State is: " + state);
+                }
+            }
+
+            synchronized (writeLock) {}
+        }
+    }
+
+    @Override
+    public SSLSession getSession() {
+        if (sslSession == null) {
+            boolean handshakeCompleted = false;
+            try {
+                if (isConnected()) {
+                    waitForHandshake();
+                    handshakeCompleted = true;
+                }
+            } catch (IOException e) {
+                // Fall through.
+            }
+
+            if (!handshakeCompleted) {
+                // return an invalid session with
+                // invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
+                return SSLNullSession.getNullSession();
+            }
+        }
+        return Platform.wrapSSLSession(sslSession);
+    }
+
+    @Override
+    SSLSession getActiveSession() {
+        return sslSession;
+    }
+
+    @Override
+    public SSLSession getHandshakeSession() {
+        return handshakeSession;
+    }
+
+    @Override
+    public boolean getEnableSessionCreation() {
+        return sslParameters.getEnableSessionCreation();
+    }
+
+    @Override
+    public void setEnableSessionCreation(boolean flag) {
+        sslParameters.setEnableSessionCreation(flag);
+    }
+
+    @Override
+    public String[] getSupportedCipherSuites() {
+        return NativeCrypto.getSupportedCipherSuites();
+    }
+
+    @Override
+    public String[] getEnabledCipherSuites() {
+        return sslParameters.getEnabledCipherSuites();
+    }
+
+    @Override
+    public void setEnabledCipherSuites(String[] suites) {
+        sslParameters.setEnabledCipherSuites(suites);
+    }
+
+    @Override
+    public String[] getSupportedProtocols() {
+        return NativeCrypto.getSupportedProtocols();
+    }
+
+    @Override
+    public String[] getEnabledProtocols() {
+        return sslParameters.getEnabledProtocols();
+    }
+
+    @Override
+    public void setEnabledProtocols(String[] protocols) {
+        sslParameters.setEnabledProtocols(protocols);
+    }
+
+    /**
+     * This method enables session ticket support.
+     *
+     * @param useSessionTickets True to enable session tickets
+     */
+    @Override
+    public void setUseSessionTickets(boolean useSessionTickets) {
+        sslParameters.setUseSessionTickets(useSessionTickets);
+    }
+
+    /**
+     * This method enables Server Name Indication
+     *
+     * @param hostname the desired SNI hostname, or null to disable
+     */
+    @Override
+    public void setHostname(String hostname) {
+        sslParameters.setUseSni(hostname != null);
+        super.setHostname(hostname);
+    }
+
+    /**
+     * Enables/disables TLS Channel ID for this server socket.
+     *
+     * <p>This method needs to be invoked before the handshake starts.
+     *
+     * @throws IllegalStateException if this is a client socket or if the handshake has already
+     *         started.
+     */
+    @Override
+    public void setChannelIdEnabled(boolean enabled) {
+        if (getUseClientMode()) {
+            throw new IllegalStateException("Client mode");
+        }
+
+        synchronized (stateLock) {
+            if (state != STATE_NEW) {
+                throw new IllegalStateException(
+                        "Could not enable/disable Channel ID after the initial handshake has"
+                                + " begun.");
+            }
+        }
+        sslParameters.channelIdEnabled = enabled;
+    }
+
+    /**
+     * Gets the TLS Channel ID for this server socket. Channel ID is only available once the
+     * handshake completes.
+     *
+     * @return channel ID or {@code null} if not available.
+     *
+     * @throws IllegalStateException if this is a client socket or if the handshake has not yet
+     *         completed.
+     * @throws SSLException if channel ID is available but could not be obtained.
+     */
+    @Override
+    public byte[] getChannelId() throws SSLException {
+        if (getUseClientMode()) {
+            throw new IllegalStateException("Client mode");
+        }
+
+        synchronized (stateLock) {
+            if (state != STATE_READY) {
+                throw new IllegalStateException(
+                        "Channel ID is only available after handshake completes");
+            }
+        }
+        return NativeCrypto.SSL_get_tls_channel_id(sslNativePointer);
+    }
+
+    /**
+     * Sets the {@link PrivateKey} to be used for TLS Channel ID by this client socket.
+     *
+     * <p>This method needs to be invoked before the handshake starts.
+     *
+     * @param privateKey private key (enables TLS Channel ID) or {@code null} for no key (disables
+     *        TLS Channel ID). The private key must be an Elliptic Curve (EC) key based on the NIST
+     *        P-256 curve (aka SECG secp256r1 or ANSI X9.62 prime256v1).
+     *
+     * @throws IllegalStateException if this is a server socket or if the handshake has already
+     *         started.
+     */
+    @Override
+    public void setChannelIdPrivateKey(PrivateKey privateKey) {
+        if (!getUseClientMode()) {
+            throw new IllegalStateException("Server mode");
+        }
+
+        synchronized (stateLock) {
+            if (state != STATE_NEW) {
+                throw new IllegalStateException(
+                        "Could not change Channel ID private key after the initial handshake has"
+                                + " begun.");
+            }
+        }
+
+        if (privateKey == null) {
+            sslParameters.channelIdEnabled = false;
+            channelIdPrivateKey = null;
+        } else {
+            sslParameters.channelIdEnabled = true;
+            try {
+                ECParameterSpec ecParams = null;
+                if (privateKey instanceof ECKey) {
+                    ecParams = ((ECKey) privateKey).getParams();
+                }
+                if (ecParams == null) {
+                    // Assume this is a P-256 key, as specified in the contract of this method.
+                    ecParams =
+                            OpenSSLECGroupContext.getCurveByName("prime256v1").getECParameterSpec();
+                }
+                channelIdPrivateKey =
+                        OpenSSLKey.fromECPrivateKeyForTLSStackOnly(privateKey, ecParams);
+            } catch (InvalidKeyException e) {
+                // Will have error in startHandshake
+            }
+        }
+    }
+
+    @Override
+    public boolean getUseClientMode() {
+        return sslParameters.getUseClientMode();
+    }
+
+    @Override
+    public void setUseClientMode(boolean mode) {
+        synchronized (stateLock) {
+            if (state != STATE_NEW) {
+                throw new IllegalArgumentException(
+                        "Could not change the mode after the initial handshake has begun.");
+            }
+        }
+        sslParameters.setUseClientMode(mode);
+    }
+
+    @Override
+    public boolean getWantClientAuth() {
+        return sslParameters.getWantClientAuth();
+    }
+
+    @Override
+    public boolean getNeedClientAuth() {
+        return sslParameters.getNeedClientAuth();
+    }
+
+    @Override
+    public void setNeedClientAuth(boolean need) {
+        sslParameters.setNeedClientAuth(need);
+    }
+
+    @Override
+    public void setWantClientAuth(boolean want) {
+        sslParameters.setWantClientAuth(want);
+    }
+
+    /**
+     * Note write timeouts are not part of the javax.net.ssl.SSLSocket API
+     */
+    @Override
+    public void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException {
+        this.writeTimeoutMilliseconds = writeTimeoutMilliseconds;
+
+        Platform.setSocketWriteTimeout(this, writeTimeoutMilliseconds);
+    }
+
+    /**
+     * Note write timeouts are not part of the javax.net.ssl.SSLSocket API
+     */
+    @Override
+    public int getSoWriteTimeout() throws SocketException {
+        return writeTimeoutMilliseconds;
+    }
+
+    /**
+     * Set the handshake timeout on this socket.  This timeout is specified in
+     * milliseconds and will be used only during the handshake process.
+     */
+    @Override
+    public void setHandshakeTimeout(int handshakeTimeoutMilliseconds) throws SocketException {
+        this.handshakeTimeoutMilliseconds = handshakeTimeoutMilliseconds;
+    }
+
+    @Override
+    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
+    public void close() throws IOException {
+        // TODO: Close SSL sockets using a background thread so they close gracefully.
+
+        SSLInputStream sslInputStream;
+        SSLOutputStream sslOutputStream;
+
+        synchronized (stateLock) {
+            if (state == STATE_CLOSED) {
+                // close() has already been called, so do nothing and return.
+                return;
+            }
+
+            int oldState = state;
+            state = STATE_CLOSED;
+
+            if (oldState == STATE_NEW) {
+                // The handshake hasn't been started yet, so there's no OpenSSL related
+                // state to clean up. We still need to close the underlying socket if
+                // we're wrapping it and were asked to autoClose.
+                closeUnderlyingSocket();
+
+                stateLock.notifyAll();
+                return;
+            }
+
+            if (oldState != STATE_READY && oldState != STATE_READY_HANDSHAKE_CUT_THROUGH) {
+                // If we're in these states, we still haven't returned from startHandshake.
+                // We call SSL_interrupt so that we can interrupt SSL_do_handshake and then
+                // set the state to STATE_CLOSED. startHandshake will handle all cleanup
+                // after SSL_do_handshake returns, so we don't have anything to do here.
+                NativeCrypto.SSL_interrupt(sslNativePointer);
+
+                stateLock.notifyAll();
+                return;
+            }
+
+            stateLock.notifyAll();
+            // We've already returned from startHandshake, so we potentially have
+            // input and output streams to clean up.
+            sslInputStream = is;
+            sslOutputStream = os;
+        }
+
+        // Don't bother interrupting unless we have something to interrupt.
+        if (sslInputStream != null || sslOutputStream != null) {
+            NativeCrypto.SSL_interrupt(sslNativePointer);
+        }
+
+        // Wait for the input and output streams to finish any reads they have in
+        // progress. If there are no reads in progress at this point, future reads will
+        // throw because state == STATE_CLOSED
+        if (sslInputStream != null) {
+            sslInputStream.awaitPendingOps();
+        }
+        if (sslOutputStream != null) {
+            sslOutputStream.awaitPendingOps();
+        }
+
+        shutdownAndFreeSslNative();
+    }
+
+    private void shutdownAndFreeSslNative() throws IOException {
+        try {
+            Platform.blockGuardOnNetwork();
+            NativeCrypto.SSL_shutdown(sslNativePointer, Platform.getFileDescriptor(socket),
+                    this);
+        } catch (IOException ignored) {
+            /*
+             * Note that although close() can throw
+             * IOException, the RI does not throw if there
+             * is problem sending a "close notify" which
+             * can happen if the underlying socket is closed.
+             */
+        } finally {
+            free();
+            closeUnderlyingSocket();
+        }
+    }
+
+    private void closeUnderlyingSocket() throws IOException {
+        super.close();
+    }
+
+    private void free() {
+        if (sslNativePointer == 0) {
+            return;
+        }
+        NativeCrypto.SSL_free(sslNativePointer);
+        sslNativePointer = 0;
+        Platform.closeGuardClose(guard);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            /*
+             * Just worry about our own state. Notably we do not try and
+             * close anything. The SocketImpl, either our own
+             * PlainSocketImpl, or the Socket we are wrapping, will do
+             * that. This might mean we do not properly SSL_shutdown, but
+             * if you want to do that, properly close the socket yourself.
+             *
+             * The reason why we don't try to SSL_shutdown, is that there
+             * can be a race between finalizers where the PlainSocketImpl
+             * finalizer runs first and closes the socket. However, in the
+             * meanwhile, the underlying file descriptor could be reused
+             * for another purpose. If we call SSL_shutdown, the
+             * underlying socket BIOs still have the old file descriptor
+             * and will write the close notify to some unsuspecting
+             * reader.
+             */
+            if (guard != null) {
+                Platform.closeGuardWarnIfOpen(guard);
+            }
+            free();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Returns the protocol agreed upon by client and server, or {@code null} if
+     * no protocol was agreed upon.
+     */
+    @Override
+    public byte[] getAlpnSelectedProtocol() {
+        return NativeCrypto.SSL_get0_alpn_selected(sslNativePointer);
+    }
+
+    /**
+     * Sets the list of ALPN protocols. This method internally converts the protocols to their
+     * wire-format form.
+     *
+     * @param alpnProtocols the list of ALPN protocols
+     * @see #setAlpnProtocols(byte[])
+     */
+    @Override
+    public void setAlpnProtocols(String[] alpnProtocols) {
+        sslParameters.setAlpnProtocols(alpnProtocols);
+    }
+
+    /**
+     * Alternate version of {@link #setAlpnProtocols(String[])} that directly sets the list of
+     * ALPN in the wire-format form used by BoringSSL (length-prefixed 8-bit strings).
+     * Requires that all strings be encoded with US-ASCII.
+     *
+     * @param alpnProtocols the encoded form of the ALPN protocol list
+     * @see #setAlpnProtocols(String[])
+     */
+    @Override
+    public void setAlpnProtocols(byte[] alpnProtocols) {
+        sslParameters.setAlpnProtocols(alpnProtocols);
+    }
+
+    @Override
+    public SSLParameters getSSLParameters() {
+        SSLParameters params = super.getSSLParameters();
+        Platform.getSSLParameters(params, sslParameters, this);
+        return params;
+    }
+
+    @Override
+    public void setSSLParameters(SSLParameters p) {
+        super.setSSLParameters(p);
+        Platform.setSSLParameters(p, sslParameters, this);
+    }
+
+    @Override
+    public String chooseServerAlias(X509KeyManager keyManager, String keyType) {
+        return keyManager.chooseServerAlias(keyType, null, this);
+    }
+
+    @Override
+    public String chooseClientAlias(X509KeyManager keyManager, X500Principal[] issuers,
+            String[] keyTypes) {
+        return keyManager.chooseClientAlias(keyTypes, null, this);
+    }
+
+    @Override
+    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
+    public String chooseServerPSKIdentityHint(PSKKeyManager keyManager) {
+        return keyManager.chooseServerKeyIdentityHint(this);
+    }
+
+    @Override
+    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
+    public String chooseClientPSKIdentity(PSKKeyManager keyManager, String identityHint) {
+        return keyManager.chooseClientKeyIdentity(identityHint, this);
+    }
+
+    @Override
+    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
+    public SecretKey getPSKKey(PSKKeyManager keyManager, String identityHint, String identity) {
+        return keyManager.getKey(identityHint, identity, this);
+    }
+}
diff --git a/common/src/main/java/org/conscrypt/OpenSSLServerSocketImpl.java b/common/src/main/java/org/conscrypt/ConscryptServerSocket.java
similarity index 82%
rename from common/src/main/java/org/conscrypt/OpenSSLServerSocketImpl.java
rename to common/src/main/java/org/conscrypt/ConscryptServerSocket.java
index a7e3654..80a758f 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLServerSocketImpl.java
+++ b/common/src/main/java/org/conscrypt/ConscryptServerSocket.java
@@ -19,32 +19,33 @@
 import java.io.IOException;
 import java.net.InetAddress;
 import java.net.Socket;
+import javax.net.ssl.SSLServerSocket;
 
 /**
  * BoringSSL-based implementation of server sockets.
  */
-final class OpenSSLServerSocketImpl extends javax.net.ssl.SSLServerSocket {
+final class ConscryptServerSocket extends SSLServerSocket {
     private final SSLParametersImpl sslParameters;
     private boolean channelIdEnabled;
     private boolean useEngineSocket;
 
-    OpenSSLServerSocketImpl(SSLParametersImpl sslParameters) throws IOException {
+    ConscryptServerSocket(SSLParametersImpl sslParameters) throws IOException {
         this.sslParameters = sslParameters;
     }
 
-    OpenSSLServerSocketImpl(int port, SSLParametersImpl sslParameters)
+    ConscryptServerSocket(int port, SSLParametersImpl sslParameters)
         throws IOException {
         super(port);
         this.sslParameters = sslParameters;
     }
 
-    OpenSSLServerSocketImpl(int port, int backlog, SSLParametersImpl sslParameters)
+    ConscryptServerSocket(int port, int backlog, SSLParametersImpl sslParameters)
         throws IOException {
         super(port, backlog);
         this.sslParameters = sslParameters;
     }
 
-    OpenSSLServerSocketImpl(int port,
+    ConscryptServerSocket(int port,
                                       int backlog,
                                       InetAddress iAddress,
                                       SSLParametersImpl sslParameters)
@@ -56,7 +57,7 @@
     /**
      * Configures the socket to be created for this instance.
      */
-    OpenSSLServerSocketImpl setUseEngineSocket(boolean useEngineSocket) {
+    ConscryptServerSocket setUseEngineSocket(boolean useEngineSocket) {
         this.useEngineSocket = useEngineSocket;
         return this;
     }
@@ -174,21 +175,15 @@
 
     @Override
     public Socket accept() throws IOException {
+        final AbstractConscryptSocket socket;
         if (useEngineSocket) {
-            Socket rawSocket = new Socket();
-            implAccept(rawSocket);
-
-            // Enable channel ID.
-            OpenSSLEngineSocketImpl socket =
-                    new OpenSSLEngineSocketImpl(rawSocket, null, -1, true, sslParameters);
-            socket.setChannelIdEnabled(channelIdEnabled);
-            socket.startHandshake();
-            return socket;
+            socket = new ConscryptEngineSocket(sslParameters);
         } else {
-            OpenSSLSocketImpl socket = new OpenSSLSocketImpl(sslParameters);
-            socket.setChannelIdEnabled(channelIdEnabled);
-            implAccept(socket);
-            return socket;
+            socket = new ConscryptFileDescriptorSocket(sslParameters);
         }
+
+        socket.setChannelIdEnabled(channelIdEnabled);
+        implAccept(socket);
+        return socket;
     }
 }
diff --git a/common/src/main/java/org/conscrypt/OpenSSLEngineSocketImpl.java b/common/src/main/java/org/conscrypt/OpenSSLEngineSocketImpl.java
deleted file mode 100644
index 2a379d2..0000000
--- a/common/src/main/java/org/conscrypt/OpenSSLEngineSocketImpl.java
+++ /dev/null
@@ -1,607 +0,0 @@
-/*
- * Copyright 2016 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;
-
-import static javax.net.ssl.SSLEngineResult.Status.OK;
-
-import java.io.EOFException;
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.SocketException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import java.security.PrivateKey;
-import java.security.cert.CertificateException;
-import javax.crypto.SecretKey;
-import javax.net.ssl.SSLEngineResult;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.X509KeyManager;
-import javax.security.auth.x500.X500Principal;
-
-/**
- * Implements crypto handling by delegating to {@link ConscryptEngine}. Used for socket
- * implementations that are not backed by a real OS socket.
- */
-final class OpenSSLEngineSocketImpl extends OpenSSLSocketImplWrapper {
-    private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
-
-    private final ConscryptEngine engine;
-    private final Socket socket;
-    private final OutputStreamWrapper outputStreamWrapper;
-    private final InputStreamWrapper inputStreamWrapper;
-    private boolean handshakeComplete;
-
-    OpenSSLEngineSocketImpl(SSLParametersImpl sslParameters) throws IOException {
-        this(new Socket(), null, -1, false, sslParameters);
-    }
-
-    OpenSSLEngineSocketImpl(String host, int port, SSLParametersImpl sslParameters)
-            throws IOException {
-        this(new Socket(host, port), host, port, false, sslParameters);
-    }
-
-    OpenSSLEngineSocketImpl(String host, int port, InetAddress clientAddress, int clientPort,
-            SSLParametersImpl sslParameters) throws IOException {
-        this(new Socket(host, port, clientAddress, clientPort), host, port, false, sslParameters);
-    }
-
-    OpenSSLEngineSocketImpl(InetAddress address, int port, SSLParametersImpl sslParameters)
-            throws IOException {
-        this(new Socket(address, port), null, port, false, sslParameters);
-    }
-
-    OpenSSLEngineSocketImpl(InetAddress address, int port, InetAddress clientAddress,
-            int clientPort, SSLParametersImpl sslParameters) throws IOException {
-        this(new Socket(address, port, clientAddress, clientPort), null, port, false,
-                sslParameters);
-    }
-
-    OpenSSLEngineSocketImpl(Socket socket, String hostname, int port, boolean autoClose,
-            SSLParametersImpl sslParameters) throws IOException {
-        super(socket, hostname, port, autoClose, sslParameters);
-        this.socket = socket;
-        engine = new ConscryptEngine(hostname, port, sslParameters);
-
-        // When the handshake completes, notify any listeners.
-        engine.setHandshakeListener(new HandshakeListener() {
-            @Override
-            public void onHandshakeFinished() {
-                if (!handshakeComplete) {
-                    handshakeComplete = true;
-                    OpenSSLEngineSocketImpl.this.notifyHandshakeCompletedListeners();
-                }
-            }
-        });
-        outputStreamWrapper = new OutputStreamWrapper();
-        inputStreamWrapper = new InputStreamWrapper();
-        engine.setUseClientMode(sslParameters.getUseClientMode());
-    }
-
-    @Override
-    public void startHandshake() throws IOException {
-        try {
-            // Trigger the handshake
-            boolean beginHandshakeCalled = false;
-            while (!handshakeComplete) {
-                switch (engine.getHandshakeStatus()) {
-                    case NOT_HANDSHAKING: {
-                        if (!beginHandshakeCalled) {
-                            beginHandshakeCalled = true;
-                            engine.beginHandshake();
-                            break;
-                        }
-                        break;
-                    }
-                    case FINISHED: {
-                        return;
-                    }
-                    case NEED_WRAP: {
-                        outputStreamWrapper.write(EMPTY_BUFFER);
-                        break;
-                    }
-                    case NEED_UNWRAP: {
-                        if (inputStreamWrapper.read(EmptyArray.BYTE) == -1) {
-                            // Can't complete the handshake due to EOF.
-                            throw SSLUtils.toSSLHandshakeException(new EOFException());
-                        }
-                        break;
-                    }
-                    case NEED_TASK: {
-                        throw new IllegalStateException("ConscryptEngine returned NEED_TASK");
-                    }
-                    default: { break; }
-                }
-            }
-        } catch (Exception e) {
-            close();
-            throw SSLUtils.toSSLHandshakeException(e);
-        }
-    }
-
-    @Override
-    public void onSSLStateChange(int type, int val) {
-        throw new AssertionError("Should be handled by engine");
-    }
-
-    @Override
-    public void verifyCertificateChain(long[] certRefs, String authMethod)
-            throws CertificateException {
-        throw new AssertionError("Should be handled by engine");
-    }
-
-    @Override
-    public InputStream getInputStream() throws IOException {
-        return inputStreamWrapper;
-    }
-
-    @Override
-    public OutputStream getOutputStream() throws IOException {
-        return outputStreamWrapper;
-    }
-
-    @Override
-    public SSLSession getSession() {
-        return engine.getSession();
-    }
-
-    @Override
-    public boolean getEnableSessionCreation() {
-        return super.getEnableSessionCreation();
-    }
-
-    @Override
-    public void setEnableSessionCreation(boolean flag) {
-        super.setEnableSessionCreation(flag);
-    }
-
-    @Override
-    public String[] getSupportedCipherSuites() {
-        return super.getSupportedCipherSuites();
-    }
-
-    @Override
-    public String[] getEnabledCipherSuites() {
-        return super.getEnabledCipherSuites();
-    }
-
-    @Override
-    public void setEnabledCipherSuites(String[] suites) {
-        super.setEnabledCipherSuites(suites);
-    }
-
-    @Override
-    public String[] getSupportedProtocols() {
-        return super.getSupportedProtocols();
-    }
-
-    @Override
-    public String[] getEnabledProtocols() {
-        return super.getEnabledProtocols();
-    }
-
-    @Override
-    public void setEnabledProtocols(String[] protocols) {
-        super.setEnabledProtocols(protocols);
-    }
-
-    @Override
-    public void setUseSessionTickets(boolean useSessionTickets) {
-        super.setUseSessionTickets(useSessionTickets);
-    }
-
-    @Override
-    public void setHostname(String hostname) {
-        super.setHostname(hostname);
-    }
-
-    @Override
-    public void setChannelIdEnabled(boolean enabled) {
-        super.setChannelIdEnabled(enabled);
-    }
-
-    @Override
-    public byte[] getChannelId() throws SSLException {
-        return super.getChannelId();
-    }
-
-    @Override
-    public void setChannelIdPrivateKey(PrivateKey privateKey) {
-        super.setChannelIdPrivateKey(privateKey);
-    }
-
-    @Override
-    public boolean getUseClientMode() {
-        return super.getUseClientMode();
-    }
-
-    @Override
-    public void setUseClientMode(boolean mode) {
-        engine.setUseClientMode(mode);
-    }
-
-    @Override
-    public boolean getWantClientAuth() {
-        return super.getWantClientAuth();
-    }
-
-    @Override
-    public boolean getNeedClientAuth() {
-        return super.getNeedClientAuth();
-    }
-
-    @Override
-    public void setNeedClientAuth(boolean need) {
-        super.setNeedClientAuth(need);
-    }
-
-    @Override
-    public void setWantClientAuth(boolean want) {
-        super.setWantClientAuth(want);
-    }
-
-    @Override
-    public void sendUrgentData(int data) throws IOException {
-        super.sendUrgentData(data);
-    }
-
-    @Override
-    public void setOOBInline(boolean on) throws SocketException {
-        super.setOOBInline(on);
-    }
-
-    @Override
-    public void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException {
-        throw new UnsupportedOperationException("Not supported");
-    }
-
-    @Override
-    public int getSoWriteTimeout() throws SocketException {
-        return 0;
-    }
-
-    @Override
-    public void setHandshakeTimeout(int handshakeTimeoutMilliseconds) throws SocketException {
-        throw new UnsupportedOperationException("Not supported");
-    }
-
-    @Override
-    public synchronized void close() throws IOException {
-        // Closing Socket.
-        engine.closeInbound();
-        engine.closeOutbound();
-        socket.close();
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        super.finalize();
-    }
-
-    @Override
-    public SocketChannel getChannel() {
-        return super.getChannel();
-    }
-
-    @Override
-    public FileDescriptor getFileDescriptor$() {
-        throw new UnsupportedOperationException("Not supported");
-    }
-
-    @Override
-    public byte[] getNpnSelectedProtocol() {
-        return null;
-    }
-
-    @Override
-    public byte[] getAlpnSelectedProtocol() {
-        return engine.getAlpnSelectedProtocol();
-    }
-
-    @Override
-    public void setNpnProtocols(byte[] npnProtocols) {
-        super.setNpnProtocols(npnProtocols);
-    }
-
-    @Override
-    public void setAlpnProtocols(byte[] alpnProtocols) {
-        super.setAlpnProtocols(alpnProtocols);
-    }
-
-    @Override
-    public String chooseServerAlias(X509KeyManager keyManager, String keyType) {
-        return engine.chooseServerAlias(keyManager, keyType);
-    }
-
-    @Override
-    public String chooseClientAlias(
-            X509KeyManager keyManager, X500Principal[] issuers, String[] keyTypes) {
-        return engine.chooseClientAlias(keyManager, issuers, keyTypes);
-    }
-
-    @Override
-    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
-    public String chooseServerPSKIdentityHint(PSKKeyManager keyManager) {
-        return engine.chooseServerPSKIdentityHint(keyManager);
-    }
-
-    @Override
-    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
-    public String chooseClientPSKIdentity(PSKKeyManager keyManager, String identityHint) {
-        return engine.chooseClientPSKIdentity(keyManager, identityHint);
-    }
-
-    @Override
-    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
-    public SecretKey getPSKKey(PSKKeyManager keyManager, String identityHint, String identity) {
-        return engine.getPSKKey(keyManager, identityHint, identity);
-    }
-
-    /**
-     * Wrap bytes written to the underlying socket.
-     */
-    private final class OutputStreamWrapper extends OutputStream {
-        private final Object stateLock = new Object();
-        private ByteBuffer target;
-        private OutputStream socketOutputStream;
-        private SocketChannel socketChannel;
-
-        OutputStreamWrapper() {}
-
-        @Override
-        public void write(int b) throws IOException {
-            write(new byte[] {(byte) b});
-        }
-
-        @Override
-        public void write(byte[] b) throws IOException {
-            write(ByteBuffer.wrap(b));
-        }
-
-        @Override
-        public void write(byte[] b, int off, int len) throws IOException {
-            write(ByteBuffer.wrap(b, off, len));
-        }
-
-        private void write(ByteBuffer buffer) throws IOException {
-            synchronized (stateLock) {
-                try {
-                    init();
-
-                    // Need to loop through at least once to enable handshaking where no application
-                    // bytes are
-                    // processed.
-                    int len = buffer.remaining();
-                    SSLEngineResult engineResult;
-                    do {
-                        target.clear();
-                        engineResult = engine.wrap(buffer, target);
-                        if (engineResult.getStatus() != OK) {
-                            throw new SSLException(
-                                    "Unexpected engine result " + engineResult.getStatus());
-                        }
-                        if (target.position() != engineResult.bytesProduced()) {
-                            throw new SSLException("Engine bytesProduced "
-                                    + engineResult.bytesProduced()
-                                    + " does not match bytes written " + target.position());
-                        }
-                        len -= engineResult.bytesConsumed();
-                        if (len != buffer.remaining()) {
-                            throw new SSLException(
-                                    "Engine did not read the correct number of bytes");
-                        }
-
-                        target.flip();
-
-                        // Write the data to the socket.
-                        if (socketChannel != null) {
-                            // Loop until all of the data is written to the channel. Typically,
-                            // SocketChannel writes will return only after all bytes are written,
-                            // so we won't really loop here.
-                            while (target.hasRemaining()) {
-                                socketChannel.write(target);
-                            }
-                        } else {
-                            // Target is a heap buffer.
-                            socketOutputStream.write(target.array(), 0, target.limit());
-                        }
-                    } while (len > 0);
-                } catch (IOException e) {
-                    throw e;
-                } catch (RuntimeException e) {
-                    throw e;
-                }
-            }
-        }
-
-        @Override
-        public void flush() throws IOException {
-            synchronized (stateLock) {
-                init();
-                socketOutputStream.flush();
-            }
-        }
-
-        @Override
-        public void close() throws IOException {
-            socket.close();
-        }
-
-        private void init() throws IOException {
-            if (socketOutputStream == null) {
-                socketOutputStream = socket.getOutputStream();
-                socketChannel = socket.getChannel();
-                if (socketChannel != null) {
-                    // Optimization. Using direct buffers wherever possible to avoid passing
-                    // arrays to JNI.
-                    target = ByteBuffer.allocateDirect(engine.getSession().getPacketBufferSize());
-                } else {
-                    target = ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
-                }
-            }
-        }
-    }
-
-    /**
-     * Unwrap bytes read from the underlying socket.
-     */
-    private final class InputStreamWrapper extends InputStream {
-        private final Object stateLock = new Object();
-        private final byte[] singleByte = new byte[1];
-        private final ByteBuffer fromEngine;
-        private ByteBuffer fromSocket;
-        private InputStream socketInputStream;
-        private SocketChannel socketChannel;
-
-        InputStreamWrapper() {
-            fromEngine = ByteBuffer.allocateDirect(engine.getSession().getApplicationBufferSize());
-            // Initially fromEngine.remaining() == 0.
-            fromEngine.flip();
-        }
-
-        @Override
-        public int read() throws IOException {
-            synchronized (stateLock) {
-                // Handle returning of -1 if EOF is reached.
-                int count = read(singleByte, 0, 1);
-                if (count == -1) {
-                    // Handle EOF.
-                    return -1;
-                }
-                if (count != 1) {
-                    throw new SSLException("read incorrect number of bytes " + count);
-                }
-                return (int) singleByte[0];
-            }
-        }
-
-        @Override
-        public int read(byte[] b) throws IOException {
-            return read(b, 0, b.length);
-        }
-
-        @Override
-        public int read(byte[] b, int off, int len) throws IOException {
-            synchronized (stateLock) {
-                try {
-                    // Make sure the input stream has been created.
-                    init();
-
-                    for (;;) {
-                        // Serve any remaining data from the engine first.
-                        if (fromEngine.remaining() > 0) {
-                            int readFromEngine = Math.min(fromEngine.remaining(), len);
-                            fromEngine.get(b, off, readFromEngine);
-                            return readFromEngine;
-                        }
-
-                        // Try to unwrap any data already in the socket buffer.
-                        boolean needMoreData = true;
-                        if (fromSocket.position() > 0) {
-                            // Unwrap the unencrypted bytes into the engine buffer.
-                            fromSocket.flip();
-                            fromEngine.clear();
-                            SSLEngineResult engineResult = engine.unwrap(fromSocket, fromEngine);
-
-                            // Shift any remaining data to the beginning of the buffer so that
-                            // we can accommodate the next full packet. After this is called,
-                            // limit will be restored to capacity and position will point just
-                            // past the end of the data.
-                            fromSocket.compact();
-                            fromEngine.flip();
-
-                            switch (engineResult.getStatus()) {
-                                case BUFFER_UNDERFLOW: {
-                                    if (engineResult.bytesProduced() == 0) {
-                                        // Need to read more data from the socket.
-                                        break;
-                                    }
-                                    // Also serve the data that was produced.
-                                    needMoreData = false;
-                                    break;
-                                }
-                                case OK: {
-                                    // We processed the entire packet successfully.
-                                    needMoreData = false;
-                                    break;
-                                }
-                                case CLOSED: {
-                                    // EOF
-                                    return -1;
-                                }
-                                default: {
-                                    // Anything else is an error.
-                                    throw new SSLException(
-                                            "Unexpected engine result " + engineResult.getStatus());
-                                }
-                            }
-
-                            if (!needMoreData && engineResult.bytesProduced() == 0) {
-                                // Read successfully, but produced no data. Possibly part of a
-                                // handshake.
-                                return 0;
-                            }
-                        }
-
-                        // Read more data from the socket.
-                        if (needMoreData && readFromSocket() == -1) {
-                            // Failed to read the next encrypted packet before reaching EOF.
-                            return -1;
-                        }
-
-                        // Continue the loop and return the data from the engine buffer.
-                    }
-                } catch (IOException e) {
-                    throw e;
-                } catch (RuntimeException e) {
-                    throw e;
-                }
-            }
-        }
-
-        private void init() throws IOException {
-            if (socketInputStream == null) {
-                socketInputStream = socket.getInputStream();
-                socketChannel = socket.getChannel();
-                if (socketChannel != null) {
-                    fromSocket =
-                            ByteBuffer.allocateDirect(engine.getSession().getPacketBufferSize());
-                } else {
-                    fromSocket = ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
-                }
-            }
-        }
-
-        private int readFromSocket() throws IOException {
-            if (socketChannel != null) {
-                return socketChannel.read(fromSocket);
-            }
-            // Read directly to the underlying array and increment the buffer position if
-            // appropriate.
-            int read = socketInputStream.read(
-                    fromSocket.array(), fromSocket.position(), fromSocket.remaining());
-            if (read > 0) {
-                fromSocket.position(fromSocket.position() + read);
-            }
-            return read;
-        }
-    }
-}
diff --git a/common/src/main/java/org/conscrypt/OpenSSLServerSocketFactoryImpl.java b/common/src/main/java/org/conscrypt/OpenSSLServerSocketFactoryImpl.java
index 008e92f..c2507d8 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLServerSocketFactoryImpl.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLServerSocketFactoryImpl.java
@@ -24,6 +24,9 @@
 
 /**
  * An implementation of {@link SSLServerSocketFactory} using BoringSSL.
+ *
+ * <p/>This name of this class cannot change in order to maintain backward-compatibility with GMS
+ * core {@code ProviderInstallerImpl}
  */
 final class OpenSSLServerSocketFactoryImpl extends SSLServerSocketFactory {
     private static boolean useEngineSocketByDefault = SSLUtils.USE_ENGINE_SOCKET_BY_DEFAULT;
@@ -37,8 +40,7 @@
             this.sslParameters = SSLParametersImpl.getDefault();
             this.sslParameters.setUseClientMode(false);
         } catch (KeyManagementException e) {
-            instantiationException =
-                new IOException("Delayed instantiation exception:");
+            instantiationException = new IOException("Delayed instantiation exception:");
             instantiationException.initCause(e);
         }
     }
@@ -75,26 +77,26 @@
 
     @Override
     public ServerSocket createServerSocket() throws IOException {
-        return new OpenSSLServerSocketImpl((SSLParametersImpl) sslParameters.clone())
+        return new ConscryptServerSocket((SSLParametersImpl) sslParameters.clone())
                 .setUseEngineSocket(useEngineSocket);
     }
 
     @Override
     public ServerSocket createServerSocket(int port) throws IOException {
-        return new OpenSSLServerSocketImpl(port, (SSLParametersImpl) sslParameters.clone())
+        return new ConscryptServerSocket(port, (SSLParametersImpl) sslParameters.clone())
                 .setUseEngineSocket(useEngineSocket);
     }
 
     @Override
     public ServerSocket createServerSocket(int port, int backlog) throws IOException {
-        return new OpenSSLServerSocketImpl(port, backlog, (SSLParametersImpl) sslParameters.clone())
+        return new ConscryptServerSocket(port, backlog, (SSLParametersImpl) sslParameters.clone())
                 .setUseEngineSocket(useEngineSocket);
     }
 
     @Override
     public ServerSocket createServerSocket(int port, int backlog, InetAddress iAddress)
             throws IOException {
-        return new OpenSSLServerSocketImpl(
+        return new ConscryptServerSocket(
                 port, backlog, iAddress, (SSLParametersImpl) sslParameters.clone())
                 .setUseEngineSocket(useEngineSocket);
     }
diff --git a/common/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java b/common/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java
index 1794d9b..3236caf 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java
@@ -19,12 +19,16 @@
 import java.io.IOException;
 import java.net.InetAddress;
 import java.net.Socket;
+import java.net.SocketException;
 import java.net.UnknownHostException;
 import java.security.KeyManagementException;
 import javax.net.ssl.SSLSocketFactory;
 
 /**
  * An implementation of {@link SSLSocketFactory} based on BoringSSL.
+ *
+ * <p/>This name of this class cannot change in order to maintain backward-compatibility with GMS
+ * core {@code ProviderInstallerImpl}
  */
 final class OpenSSLSocketFactoryImpl extends SSLSocketFactory {
     private static boolean useEngineSocketByDefault = SSLUtils.USE_ENGINE_SOCKET_BY_DEFAULT;
@@ -81,18 +85,20 @@
             throw instantiationException;
         }
         if (useEngineSocket) {
-            return new OpenSSLEngineSocketImpl((SSLParametersImpl) sslParameters.clone());
+            return new ConscryptEngineSocket((SSLParametersImpl) sslParameters.clone());
         } else {
-            return new OpenSSLSocketImpl((SSLParametersImpl) sslParameters.clone());
+            return new ConscryptFileDescriptorSocket((SSLParametersImpl) sslParameters.clone());
         }
     }
 
     @Override
     public Socket createSocket(String hostname, int port) throws IOException, UnknownHostException {
         if (useEngineSocket) {
-            return new OpenSSLEngineSocketImpl(hostname, port, (SSLParametersImpl) sslParameters.clone());
+            return new ConscryptEngineSocket(
+                    hostname, port, (SSLParametersImpl) sslParameters.clone());
         } else {
-            return new OpenSSLSocketImpl(hostname, port, (SSLParametersImpl) sslParameters.clone());
+            return new ConscryptFileDescriptorSocket(
+                    hostname, port, (SSLParametersImpl) sslParameters.clone());
         }
     }
 
@@ -100,16 +106,10 @@
     public Socket createSocket(String hostname, int port, InetAddress localHost, int localPort)
             throws IOException, UnknownHostException {
         if (useEngineSocket) {
-            return new OpenSSLEngineSocketImpl(hostname,
-                    port,
-                    localHost,
-                    localPort,
+            return new ConscryptEngineSocket(hostname, port, localHost, localPort,
                     (SSLParametersImpl) sslParameters.clone());
         } else {
-            return new OpenSSLSocketImpl(hostname,
-                    port,
-                    localHost,
-                    localPort,
+            return new ConscryptFileDescriptorSocket(hostname, port, localHost, localPort,
                     (SSLParametersImpl) sslParameters.clone());
         }
     }
@@ -117,48 +117,46 @@
     @Override
     public Socket createSocket(InetAddress address, int port) throws IOException {
         if (useEngineSocket) {
-            return new OpenSSLEngineSocketImpl(address, port, (SSLParametersImpl) sslParameters.clone());
+            return new ConscryptEngineSocket(
+                    address, port, (SSLParametersImpl) sslParameters.clone());
         } else {
-            return new OpenSSLSocketImpl(address, port, (SSLParametersImpl) sslParameters.clone());
+            return new ConscryptFileDescriptorSocket(
+                    address, port, (SSLParametersImpl) sslParameters.clone());
         }
     }
 
     @Override
-    public Socket createSocket(InetAddress address,
-                               int port,
-                               InetAddress localAddress,
-                               int localPort)
-            throws IOException {
+    public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
+            int localPort) throws IOException {
         if (useEngineSocket) {
-            return new OpenSSLEngineSocketImpl(address,
-                    port,
-                    localAddress,
-                    localPort,
+            return new ConscryptEngineSocket(address, port, localAddress, localPort,
                     (SSLParametersImpl) sslParameters.clone());
         } else {
-            return new OpenSSLSocketImpl(address,
-                    port,
-                    localAddress,
-                    localPort,
+            return new ConscryptFileDescriptorSocket(address, port, localAddress, localPort,
                     (SSLParametersImpl) sslParameters.clone());
         }
     }
 
     @Override
-    public Socket createSocket(Socket s, String hostname, int port, boolean autoClose)
+    public Socket createSocket(Socket socket, String hostname, int port, boolean autoClose)
             throws IOException {
-        if (hasFileDescriptor(s) && !useEngineSocket) {
-            return new OpenSSLSocketImplWrapper(
-                    s, hostname, port, autoClose, (SSLParametersImpl) sslParameters.clone());
+        Preconditions.checkNotNull(socket, "socket");
+        if (!socket.isConnected()) {
+            throw new SocketException("Socket is not connected.");
+        }
+
+        if (hasFileDescriptor(socket) && !useEngineSocket) {
+            return new ConscryptFileDescriptorSocket(
+                    socket, hostname, port, autoClose, (SSLParametersImpl) sslParameters.clone());
         } else {
-            return new OpenSSLEngineSocketImpl(
-                    s, hostname, port, autoClose, (SSLParametersImpl) sslParameters.clone());
+            return new ConscryptEngineSocket(
+                    socket, hostname, port, autoClose, (SSLParametersImpl) sslParameters.clone());
         }
     }
 
     private boolean hasFileDescriptor(Socket s) {
         try {
-            // If socket has a file descriptor we can use OpenSSLSocketImplWrapper directly
+            // If socket has a file descriptor we can use it directly
             // otherwise we need to use the engine.
             Platform.getFileDescriptor(s);
             return true;
diff --git a/common/src/main/java/org/conscrypt/OpenSSLSocketImpl.java b/common/src/main/java/org/conscrypt/OpenSSLSocketImpl.java
index 60f6dd3..b33f9e2 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLSocketImpl.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLSocketImpl.java
@@ -16,1299 +16,116 @@
 
 package org.conscrypt;
 
-import static org.conscrypt.SSLUtils.EngineStates.STATE_CLOSED;
-import static org.conscrypt.SSLUtils.EngineStates.STATE_HANDSHAKE_COMPLETED;
-import static org.conscrypt.SSLUtils.EngineStates.STATE_HANDSHAKE_STARTED;
-import static org.conscrypt.SSLUtils.EngineStates.STATE_NEW;
-import static org.conscrypt.SSLUtils.EngineStates.STATE_READY;
-import static org.conscrypt.SSLUtils.EngineStates.STATE_READY_HANDSHAKE_CUT_THROUGH;
-
 import java.io.FileDescriptor;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.net.InetAddress;
-import java.net.InetSocketAddress;
 import java.net.Socket;
-import java.net.SocketAddress;
 import java.net.SocketException;
-import java.security.InvalidKeyException;
 import java.security.PrivateKey;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.interfaces.ECKey;
-import java.security.spec.ECParameterSpec;
-import java.util.ArrayList;
-import javax.crypto.SecretKey;
-import javax.net.ssl.HandshakeCompletedEvent;
-import javax.net.ssl.HandshakeCompletedListener;
 import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.SSLParameters;
-import javax.net.ssl.SSLProtocolException;
 import javax.net.ssl.SSLSession;
-import javax.net.ssl.X509KeyManager;
-import javax.net.ssl.X509TrustManager;
-import javax.security.auth.x500.X500Principal;
 
 /**
- * Implementation of the class OpenSSLSocketImpl based on OpenSSL.
- * <p>
- * Extensions to SSLSocket include:
- * <ul>
- * <li>handshake timeout
- * <li>session tickets
- * <li>Server Name Indication
- * </ul>
+ * Public shim allowing us to stay backward-compatible with legacy applications which were using
+ * Conscrypt's extended socket API before the introduction of the {@link Conscrypt} class.
  *
  * @hide
  */
 @Internal
-public class OpenSSLSocketImpl
-        extends javax.net.ssl.SSLSocket
-        implements NativeCrypto.SSLHandshakeCallbacks, SSLParametersImpl.AliasChooser,
-        SSLParametersImpl.PSKCallbacks {
-
-    private static final boolean DBG_STATE = false;
-
-    /**
-     * Protects handshakeStarted and handshakeCompleted.
-     */
-    private final Object stateLock = new Object();
-
-
-    // @GuardedBy("stateLock");
-    private int state = STATE_NEW;
-
-    /**
-     * Protected by synchronizing on stateLock. Starts as 0, set by
-     * startHandshake, reset to 0 on close.
-     */
-    // @GuardedBy("stateLock");
-    private long sslNativePointer;
-
-    /**
-     * Protected by synchronizing on stateLock. Starts as null, set by
-     * getInputStream.
-     */
-    // @GuardedBy("stateLock");
-    private SSLInputStream is;
-
-    /**
-     * Protected by synchronizing on stateLock. Starts as null, set by
-     * getInputStream.
-     */
-    // @GuardedBy("stateLock");
-    private SSLOutputStream os;
-
-    private final Socket socket;
-    private final boolean autoClose;
-
-    /**
-     * The peer's DNS hostname if it was supplied during creation. Note that
-     * this may be a raw IP address, so it should be checked before use with
-     * extensions that don't use it like Server Name Indication (SNI).
-     */
-    private String peerHostname;
-
-    /**
-     * The peer's port if it was supplied during creation. Should only be set if
-     * {@link #peerHostname} is also set.
-     */
-    private final int peerPort;
-
-    private final SSLParametersImpl sslParameters;
-
-    /*
-     * A CloseGuard object on Android. On other platforms, this is nothing.
-     */
-    private final Object guard = Platform.closeGuardGet();
-
-    private ArrayList<HandshakeCompletedListener> listeners;
-
-    /**
-     * Private key for the TLS Channel ID extension. This field is client-side
-     * only. Set during startHandshake.
-     */
-    private OpenSSLKey channelIdPrivateKey;
-
-    /** Set during startHandshake. */
-    private AbstractOpenSSLSession sslSession;
-
-    /** Used during handshake callbacks. */
-    private AbstractOpenSSLSession handshakeSession;
-
-    /**
-     * Local cache of timeout to avoid getsockopt on every read and
-     * write for non-wrapped sockets. Note that
-     * OpenSSLSocketImplWrapper overrides setSoTimeout and
-     * getSoTimeout to delegate to the wrapped socket.
-     */
-    private int readTimeoutMilliseconds = 0;
-    private int writeTimeoutMilliseconds = 0;
-
-    private int handshakeTimeoutMilliseconds = -1;  // -1 = same as timeout; 0 = infinite
-
-    OpenSSLSocketImpl(SSLParametersImpl sslParameters) throws IOException {
-        this.socket = this;
-        this.peerHostname = null;
-        this.peerPort = -1;
-        this.autoClose = false;
-        this.sslParameters = sslParameters;
+public abstract class OpenSSLSocketImpl extends AbstractConscryptSocket {
+    OpenSSLSocketImpl() throws IOException {
     }
 
-    OpenSSLSocketImpl(String hostname, int port, SSLParametersImpl sslParameters)
-            throws IOException {
+    OpenSSLSocketImpl(String hostname, int port) throws IOException {
         super(hostname, port);
-        this.socket = this;
-        this.peerHostname = hostname;
-        this.peerPort = port;
-        this.autoClose = false;
-        this.sslParameters = sslParameters;
     }
 
-    OpenSSLSocketImpl(InetAddress address, int port, SSLParametersImpl sslParameters)
-            throws IOException {
+    OpenSSLSocketImpl(InetAddress address, int port) throws IOException {
         super(address, port);
-        this.socket = this;
-        this.peerHostname = null;
-        this.peerPort = -1;
-        this.autoClose = false;
-        this.sslParameters = sslParameters;
     }
 
-
-    OpenSSLSocketImpl(String hostname, int port,
-                                InetAddress clientAddress, int clientPort,
-                                SSLParametersImpl sslParameters) throws IOException {
+    OpenSSLSocketImpl(String hostname, int port, InetAddress clientAddress, int clientPort)
+        throws IOException {
         super(hostname, port, clientAddress, clientPort);
-        this.socket = this;
-        this.peerHostname = hostname;
-        this.peerPort = port;
-        this.autoClose = false;
-        this.sslParameters = sslParameters;
     }
 
-    OpenSSLSocketImpl(InetAddress address, int port,
-                                InetAddress clientAddress, int clientPort,
-                                SSLParametersImpl sslParameters) throws IOException {
+    OpenSSLSocketImpl(InetAddress address, int port, InetAddress clientAddress,
+        int clientPort)
+        throws IOException {
         super(address, port, clientAddress, clientPort);
-        this.socket = this;
-        this.peerHostname = null;
-        this.peerPort = -1;
-        this.autoClose = false;
-        this.sslParameters = sslParameters;
     }
 
-    /**
-     * Create an SSL socket that wraps another socket. Invoked by
-     * OpenSSLSocketImplWrapper constructor.
-     */
-    OpenSSLSocketImpl(Socket socket, String hostname, int port,
-            boolean autoClose, SSLParametersImpl sslParameters) throws IOException {
-        this.socket = socket;
-        this.peerHostname = hostname;
-        this.peerPort = port;
-        this.autoClose = autoClose;
-        this.sslParameters = sslParameters;
-
-        // this.timeout is not set intentionally.
-        // OpenSSLSocketImplWrapper.getSoTimeout will delegate timeout
-        // to wrapped socket
+    OpenSSLSocketImpl(Socket socket, String hostname, int port, boolean autoClose)
+        throws IOException {
+        super(socket, hostname, port, autoClose);
     }
 
     @Override
-    public void connect(SocketAddress endpoint) throws IOException {
-        connect(endpoint, 0);
-    }
-
-    /**
-     * Try to extract the peer's hostname if it's available from the endpoint address.
-     */
-    @Override
-    public void connect(SocketAddress endpoint, int timeout) throws IOException {
-        if (peerHostname == null && endpoint instanceof InetSocketAddress) {
-            peerHostname = Platform.getHostStringFromInetSocketAddress(
-                    (InetSocketAddress) endpoint);
-        }
-
-        super.connect(endpoint, timeout);
-    }
-
-    private void checkOpen() throws SocketException {
-        if (isClosed()) {
-            throw new SocketException("Socket is closed");
-        }
-    }
-
-    /**
-     * Starts a TLS/SSL handshake on this connection using some native methods
-     * from the OpenSSL library. It can negotiate new encryption keys, change
-     * cipher suites, or initiate a new session. The certificate chain is
-     * verified if the correspondent property in java.Security is set. All
-     * listeners are notified at the end of the TLS/SSL handshake.
-     */
-    @Override
-    public void startHandshake() throws IOException {
-        checkOpen();
-        synchronized (stateLock) {
-            if (state == STATE_NEW) {
-                state = STATE_HANDSHAKE_STARTED;
-            } else {
-                // We've either started the handshake already or have been closed.
-                // Do nothing in both cases.
-                return;
-            }
-        }
-
-        final boolean client = sslParameters.getUseClientMode();
-
-        sslNativePointer = 0;
-        boolean releaseResources = true;
-        try {
-            final AbstractSessionContext sessionContext = sslParameters.getSessionContext();
-            sslNativePointer = NativeCrypto.SSL_new(sessionContext.sslCtxNativePointer);
-            Platform.closeGuardOpen(guard, "close");
-
-            boolean enableSessionCreation = getEnableSessionCreation();
-            if (!enableSessionCreation) {
-                NativeCrypto.SSL_set_session_creation_enabled(sslNativePointer,
-                        enableSessionCreation);
-            }
-
-            // Allow servers to trigger renegotiation. Some inadvisable server
-            // configurations cause them to attempt to renegotiate during
-            // certain protocols.
-            NativeCrypto.SSL_accept_renegotiations(sslNativePointer);
-
-            if (client) {
-                NativeCrypto.SSL_set_connect_state(sslNativePointer);
-
-                // Configure OCSP and CT extensions for client
-                NativeCrypto.SSL_enable_ocsp_stapling(sslNativePointer);
-                if (sslParameters.isCTVerificationEnabled(getHostname())) {
-                    NativeCrypto.SSL_enable_signed_cert_timestamps(sslNativePointer);
-                }
-            } else {
-                NativeCrypto.SSL_set_accept_state(sslNativePointer);
-
-                // Configure OCSP for server
-                if (sslParameters.getOCSPResponse() != null) {
-                    NativeCrypto.SSL_enable_ocsp_stapling(sslNativePointer);
-                }
-            }
-
-            final AbstractOpenSSLSession sessionToReuse =
-                    sslParameters.getSessionToReuse(sslNativePointer, getHostnameOrIP(), getPort());
-            sslParameters.setSSLParameters(sslNativePointer, this, this, getHostname());
-            sslParameters.setCertificateValidation(sslNativePointer);
-            sslParameters.setTlsChannelId(sslNativePointer, channelIdPrivateKey);
-
-            // Temporarily use a different timeout for the handshake process
-            int savedReadTimeoutMilliseconds = getSoTimeout();
-            int savedWriteTimeoutMilliseconds = getSoWriteTimeout();
-            if (handshakeTimeoutMilliseconds >= 0) {
-                setSoTimeout(handshakeTimeoutMilliseconds);
-                setSoWriteTimeout(handshakeTimeoutMilliseconds);
-            }
-
-            synchronized (stateLock) {
-                if (state == STATE_CLOSED) {
-                    return;
-                }
-            }
-
-            long sslSessionNativePointer;
-            try {
-                NativeCrypto.SSL_do_handshake(
-                        sslNativePointer, Platform.getFileDescriptor(socket), this, getSoTimeout());
-                sslSessionNativePointer = NativeCrypto.SSL_get1_session(sslNativePointer);
-            } catch (CertificateException e) {
-                SSLHandshakeException wrapper = new SSLHandshakeException(e.getMessage());
-                wrapper.initCause(e);
-                throw wrapper;
-            } catch (SSLException e) {
-                // Swallow this exception if it's thrown as the result of an interruption.
-                //
-                // TODO: SSL_read and SSL_write return -1 when interrupted, but SSL_do_handshake
-                // will throw the last sslError that it saw before sslSelect, usually SSL_WANT_READ
-                // (or WANT_WRITE). Catching that exception here doesn't seem much worse than
-                // changing the native code to return a "special" native pointer value when that
-                // happens.
-                synchronized (stateLock) {
-                    if (state == STATE_CLOSED) {
-                        return;
-                    }
-                }
-
-                // Write CCS errors to EventLog
-                String message = e.getMessage();
-                // Must match error string of SSL_R_UNEXPECTED_CCS
-                if (message.contains("unexpected CCS")) {
-                    String logMessage = String.format("ssl_unexpected_ccs: host=%s",
-                            getHostnameOrIP());
-                    Platform.logEvent(logMessage);
-                }
-
-                throw e;
-            }
-
-            boolean handshakeCompleted = false;
-            synchronized (stateLock) {
-                if (state == STATE_HANDSHAKE_COMPLETED) {
-                    handshakeCompleted = true;
-                } else if (state == STATE_CLOSED) {
-                    return;
-                }
-            }
-
-            sslSession = sslParameters.setupSession(sslSessionNativePointer, sslNativePointer,
-                    sessionToReuse, getHostnameOrIP(), getPort(), handshakeCompleted);
-
-            // Restore the original timeout now that the handshake is complete
-            if (handshakeTimeoutMilliseconds >= 0) {
-                setSoTimeout(savedReadTimeoutMilliseconds);
-                setSoWriteTimeout(savedWriteTimeoutMilliseconds);
-            }
-
-            // if not, notifyHandshakeCompletedListeners later in handshakeCompleted() callback
-            if (handshakeCompleted) {
-                notifyHandshakeCompletedListeners();
-            }
-
-            synchronized (stateLock) {
-                releaseResources = (state == STATE_CLOSED);
-
-                if (state == STATE_HANDSHAKE_STARTED) {
-                    state = STATE_READY_HANDSHAKE_CUT_THROUGH;
-                } else if (state == STATE_HANDSHAKE_COMPLETED) {
-                    state = STATE_READY;
-                }
-
-                if (!releaseResources) {
-                    // Unblock threads that are waiting for our state to transition
-                    // into STATE_READY or STATE_READY_HANDSHAKE_CUT_THROUGH.
-                    stateLock.notifyAll();
-                }
-            }
-        } catch (SSLProtocolException e) {
-            throw (SSLHandshakeException) new SSLHandshakeException("Handshake failed")
-                    .initCause(e);
-        } finally {
-            // on exceptional exit, treat the socket as closed
-            if (releaseResources) {
-                synchronized (stateLock) {
-                    // Mark the socket as closed since we might have reached this as
-                    // a result on an exception thrown by the handshake process.
-                    //
-                    // The state will already be set to closed if we reach this as a result of
-                    // an early return or an interruption due to a concurrent call to close().
-                    state = STATE_CLOSED;
-                    stateLock.notifyAll();
-                }
-
-                try {
-                    shutdownAndFreeSslNative();
-                } catch (IOException ignored) {
-                    // Ignored.
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns the hostname that was supplied during socket creation. No DNS resolution is
-     * attempted before returning the hostname.
-     */
     public String getHostname() {
-        return peerHostname;
-    }
-
-    /**
-     * For the purposes of an SSLSession, we want a way to represent the supplied hostname
-     * or the IP address in a textual representation. We do not want to perform reverse DNS
-     * lookups on this address.
-     */
-    public String getHostnameOrIP() {
-        if (peerHostname != null) {
-            return peerHostname;
-        }
-
-        InetAddress peerAddress = getInetAddress();
-        if (peerAddress != null) {
-            return peerAddress.getHostAddress();
-        }
-
-        return null;
+        return super.getHostname();
     }
 
     @Override
-    public int getPort() {
-        return peerPort == -1 ? super.getPort() : peerPort;
-    }
-
-    @Override
-    @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks / client_cert_cb
-    public void clientCertificateRequested(byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals)
-            throws CertificateEncodingException, SSLException {
-        sslParameters.chooseClientCertificate(keyTypeBytes, asn1DerEncodedPrincipals,
-                sslNativePointer, this);
-    }
-
-    @Override
-    @SuppressWarnings("unused") // used by native psk_client_callback
-    public int clientPSKKeyRequested(String identityHint, byte[] identity, byte[] key) {
-        return sslParameters.clientPSKKeyRequested(identityHint, identity, key, this);
-    }
-
-    @Override
-    @SuppressWarnings("unused") // used by native psk_server_callback
-    public int serverPSKKeyRequested(String identityHint, String identity, byte[] key) {
-        return sslParameters.serverPSKKeyRequested(identityHint, identity, key, this);
-    }
-
-    @Override
-    @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks / info_callback
-    public void onSSLStateChange(int type, int val) {
-        if (type != NativeConstants.SSL_CB_HANDSHAKE_DONE) {
-            return;
-        }
-
-        synchronized (stateLock) {
-            if (state == STATE_HANDSHAKE_STARTED) {
-                // If sslSession is null, the handshake was completed during
-                // the call to NativeCrypto.SSL_do_handshake and not during a
-                // later read operation. That means we do not need to fix up
-                // the SSLSession and session cache or notify
-                // HandshakeCompletedListeners, it will be done in
-                // startHandshake.
-
-                state = STATE_HANDSHAKE_COMPLETED;
-                return;
-            } else if (state == STATE_READY_HANDSHAKE_CUT_THROUGH) {
-                // We've returned from startHandshake, which means we've set a sslSession etc.
-                // we need to fix them up, which we'll do outside this lock.
-            } else if (state == STATE_CLOSED) {
-                // Someone called "close" but the handshake hasn't been interrupted yet.
-                return;
-            }
-        }
-
-        // reset session id from the native pointer and update the
-        // appropriate cache.
-        sslSession.resetId();
-        AbstractSessionContext sessionContext =
-            (sslParameters.getUseClientMode())
-            ? sslParameters.getClientSessionContext()
-                : sslParameters.getServerSessionContext();
-        sessionContext.putSession(sslSession);
-
-        // let listeners know we are finally done
-        notifyHandshakeCompletedListeners();
-
-        synchronized (stateLock) {
-            // Now that we've fixed up our state, we can tell waiting threads that
-            // we're ready.
-            state = STATE_READY;
-            // Notify all threads waiting for the handshake to complete.
-            stateLock.notifyAll();
-        }
-    }
-
-    void notifyHandshakeCompletedListeners() {
-        if (listeners != null && !listeners.isEmpty()) {
-            // notify the listeners
-            HandshakeCompletedEvent event =
-                new HandshakeCompletedEvent(this, sslSession);
-            for (HandshakeCompletedListener listener : listeners) {
-                try {
-                    listener.handshakeCompleted(event);
-                } catch (RuntimeException e) {
-                    // The RI runs the handlers in a separate thread,
-                    // which we do not. But we try to preserve their
-                    // behavior of logging a problem and not killing
-                    // the handshaking thread just because a listener
-                    // has a problem.
-                    Thread thread = Thread.currentThread();
-                    thread.getUncaughtExceptionHandler().uncaughtException(thread, e);
-                }
-            }
-        }
-    }
-
-    @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks
-    @Override
-    public void verifyCertificateChain(long[] certRefs, String authMethod)
-            throws CertificateException {
-        try {
-            X509TrustManager x509tm = sslParameters.getX509TrustManager();
-            if (x509tm == null) {
-                throw new CertificateException("No X.509 TrustManager");
-            }
-            if (certRefs == null || certRefs.length == 0) {
-                throw new SSLException("Peer sent no certificate");
-            }
-            OpenSSLX509Certificate[] peerCertChain =
-                    OpenSSLX509Certificate.createCertChain(certRefs);
-
-            byte[] ocspData = NativeCrypto.SSL_get_ocsp_response(sslNativePointer);
-            byte[] tlsSctData = NativeCrypto.SSL_get_signed_cert_timestamp_list(sslNativePointer);
-
-            // Used for verifyCertificateChain callback
-            handshakeSession = new OpenSSLSessionImpl(
-                    NativeCrypto.SSL_get1_session(sslNativePointer), null, peerCertChain, ocspData,
-                    tlsSctData, getHostnameOrIP(), getPort(), null);
-
-            boolean client = sslParameters.getUseClientMode();
-            if (client) {
-                Platform.checkServerTrusted(x509tm, peerCertChain, authMethod, this);
-            } else {
-                String authType = peerCertChain[0].getPublicKey().getAlgorithm();
-                Platform.checkClientTrusted(x509tm, peerCertChain, authType, this);
-            }
-        } catch (CertificateException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new CertificateException(e);
-        } finally {
-            // Clear this before notifying handshake completed listeners
-            handshakeSession = null;
-        }
-    }
-
-    @Override
-    public InputStream getInputStream() throws IOException {
-        checkOpen();
-
-        InputStream returnVal;
-        synchronized (stateLock) {
-            if (state == STATE_CLOSED) {
-                throw new SocketException("Socket is closed.");
-            }
-
-            if (is == null) {
-                is = new SSLInputStream();
-            }
-
-            returnVal = is;
-        }
-
-        // Block waiting for a handshake without a lock held. It's possible that the socket
-        // is closed at this point. If that happens, we'll still return the input stream but
-        // all reads on it will throw.
-        waitForHandshake();
-        return returnVal;
-    }
-
-    @Override
-    public OutputStream getOutputStream() throws IOException {
-        checkOpen();
-
-        OutputStream returnVal;
-        synchronized (stateLock) {
-            if (state == STATE_CLOSED) {
-                throw new SocketException("Socket is closed.");
-            }
-
-            if (os == null) {
-                os = new SSLOutputStream();
-            }
-
-            returnVal = os;
-        }
-
-        // Block waiting for a handshake without a lock held. It's possible that the socket
-        // is closed at this point. If that happens, we'll still return the output stream but
-        // all writes on it will throw.
-        waitForHandshake();
-        return returnVal;
-    }
-
-    private void assertReadableOrWriteableState() {
-        if (state == STATE_READY || state == STATE_READY_HANDSHAKE_CUT_THROUGH) {
-            return;
-        }
-
-        throw new AssertionError("Invalid state: " + state);
-    }
-
-
-    private void waitForHandshake() throws IOException {
-        startHandshake();
-
-        synchronized (stateLock) {
-            while (state != STATE_READY &&
-                    state != STATE_READY_HANDSHAKE_CUT_THROUGH &&
-                    state != STATE_CLOSED) {
-                try {
-                    stateLock.wait();
-                } catch (InterruptedException e) {
-                    Thread.currentThread().interrupt();
-                    throw new IOException("Interrupted waiting for handshake", e);
-                }
-            }
-
-            if (state == STATE_CLOSED) {
-                throw new SocketException("Socket is closed");
-            }
-        }
-    }
-
-    /**
-     * This inner class provides input data stream functionality
-     * for the OpenSSL native implementation. It is used to
-     * read data received via SSL protocol.
-     */
-    private class SSLInputStream extends InputStream {
-        /**
-         * OpenSSL only lets one thread read at a time, so this is used to
-         * make sure we serialize callers of SSL_read. Thread is already
-         * expected to have completed handshaking.
-         */
-        private final Object readLock = new Object();
-
-        SSLInputStream() {
-        }
-
-        /**
-         * Reads one byte. If there is no data in the underlying buffer,
-         * this operation can block until the data will be
-         * available.
-         */
-        @Override
-        public int read() throws IOException {
-            byte[] buffer = new byte[1];
-            int result = read(buffer, 0, 1);
-            return (result != -1) ? buffer[0] & 0xff : -1;
-        }
-
-        /**
-         * Method acts as described in spec for superclass.
-         * @see java.io.InputStream#read(byte[],int,int)
-         */
-        @Override
-        public int read(byte[] buf, int offset, int byteCount) throws IOException {
-            Platform.blockGuardOnNetwork();
-
-            checkOpen();
-            ArrayUtils.checkOffsetAndCount(buf.length, offset, byteCount);
-            if (byteCount == 0) {
-                return 0;
-            }
-
-            synchronized (readLock) {
-                synchronized (stateLock) {
-                    if (state == STATE_CLOSED) {
-                        throw new SocketException("socket is closed");
-                    }
-
-                    if (DBG_STATE) assertReadableOrWriteableState();
-                }
-
-                return NativeCrypto.SSL_read(sslNativePointer, Platform.getFileDescriptor(socket),
-                        OpenSSLSocketImpl.this, buf, offset, byteCount, getSoTimeout());
-            }
-        }
-
-        void awaitPendingOps() {
-            if (DBG_STATE) {
-                synchronized (stateLock) {
-                    if (state != STATE_CLOSED) throw new AssertionError("State is: " + state);
-                }
-            }
-
-            synchronized (readLock) { }
-        }
-    }
-
-    /**
-     * This inner class provides output data stream functionality
-     * for the OpenSSL native implementation. It is used to
-     * write data according to the encryption parameters given in SSL context.
-     */
-    private class SSLOutputStream extends OutputStream {
-
-        /**
-         * OpenSSL only lets one thread write at a time, so this is used
-         * to make sure we serialize callers of SSL_write. Thread is
-         * already expected to have completed handshaking.
-         */
-        private final Object writeLock = new Object();
-
-        SSLOutputStream() {
-        }
-
-        /**
-         * Method acts as described in spec for superclass.
-         * @see java.io.OutputStream#write(int)
-         */
-        @Override
-        public void write(int oneByte) throws IOException {
-            byte[] buffer = new byte[1];
-            buffer[0] = (byte) (oneByte & 0xff);
-            write(buffer);
-        }
-
-        /**
-         * Method acts as described in spec for superclass.
-         * @see java.io.OutputStream#write(byte[],int,int)
-         */
-        @Override
-        public void write(byte[] buf, int offset, int byteCount) throws IOException {
-            Platform.blockGuardOnNetwork();
-            checkOpen();
-            ArrayUtils.checkOffsetAndCount(buf.length, offset, byteCount);
-            if (byteCount == 0) {
-                return;
-            }
-
-            synchronized (writeLock) {
-                synchronized (stateLock) {
-                    if (state == STATE_CLOSED) {
-                        throw new SocketException("socket is closed");
-                    }
-
-                    if (DBG_STATE) assertReadableOrWriteableState();
-                }
-
-                NativeCrypto.SSL_write(sslNativePointer, Platform.getFileDescriptor(socket),
-                        OpenSSLSocketImpl.this, buf, offset, byteCount, writeTimeoutMilliseconds);
-            }
-        }
-
-
-        void awaitPendingOps() {
-            if (DBG_STATE) {
-                synchronized (stateLock) {
-                    if (state != STATE_CLOSED) throw new AssertionError("State is: " + state);
-                }
-            }
-
-            synchronized (writeLock) { }
-        }
-    }
-
-
-    @Override
-    public SSLSession getSession() {
-        if (sslSession == null) {
-            boolean handshakeCompleted = false;
-            try {
-                if (isConnected()) {
-                    waitForHandshake();
-                    handshakeCompleted = true;
-                }
-            } catch (IOException e) {
-                // Fall through.
-            }
-
-            if (!handshakeCompleted) {
-                // return an invalid session with
-                // invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
-                return SSLNullSession.getNullSession();
-            }
-        }
-        return Platform.wrapSSLSession(sslSession);
-    }
-
-    /* @Override */
-    @SuppressWarnings("MissingOverride")  // For compilation with Java 6.
-    public SSLSession getHandshakeSession() {
-        return handshakeSession;
-    }
-
-    @Override
-    public void addHandshakeCompletedListener(
-            HandshakeCompletedListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("Provided listener is null");
-        }
-        if (listeners == null) {
-            listeners = new ArrayList<HandshakeCompletedListener>();
-        }
-        listeners.add(listener);
-    }
-
-    @Override
-    public void removeHandshakeCompletedListener(
-            HandshakeCompletedListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("Provided listener is null");
-        }
-        if (listeners == null) {
-            throw new IllegalArgumentException(
-                    "Provided listener is not registered");
-        }
-        if (!listeners.remove(listener)) {
-            throw new IllegalArgumentException(
-                    "Provided listener is not registered");
-        }
-    }
-
-    @Override
-    public boolean getEnableSessionCreation() {
-        return sslParameters.getEnableSessionCreation();
-    }
-
-    @Override
-    public void setEnableSessionCreation(boolean flag) {
-        sslParameters.setEnableSessionCreation(flag);
-    }
-
-    @Override
-    public String[] getSupportedCipherSuites() {
-        return NativeCrypto.getSupportedCipherSuites();
-    }
-
-    @Override
-    public String[] getEnabledCipherSuites() {
-        return sslParameters.getEnabledCipherSuites();
-    }
-
-    @Override
-    public void setEnabledCipherSuites(String[] suites) {
-        sslParameters.setEnabledCipherSuites(suites);
-    }
-
-    @Override
-    public String[] getSupportedProtocols() {
-        return NativeCrypto.getSupportedProtocols();
-    }
-
-    @Override
-    public String[] getEnabledProtocols() {
-        return sslParameters.getEnabledProtocols();
-    }
-
-    @Override
-    public void setEnabledProtocols(String[] protocols) {
-        sslParameters.setEnabledProtocols(protocols);
-    }
-
-    /**
-     * This method enables session ticket support.
-     *
-     * @param useSessionTickets True to enable session tickets
-     */
-    public void setUseSessionTickets(boolean useSessionTickets) {
-        sslParameters.setUseSessionTickets(useSessionTickets);
-    }
-
-    /**
-     * This method enables Server Name Indication
-     *
-     * @param hostname the desired SNI hostname, or null to disable
-     */
     public void setHostname(String hostname) {
-        sslParameters.setUseSni(hostname != null);
-        peerHostname = hostname;
-    }
-
-    /**
-     * Enables/disables TLS Channel ID for this server socket.
-     *
-     * <p>This method needs to be invoked before the handshake starts.
-     *
-     * @throws IllegalStateException if this is a client socket or if the handshake has already
-     *         started.
-     */
-    public void setChannelIdEnabled(boolean enabled) {
-        if (getUseClientMode()) {
-            throw new IllegalStateException("Client mode");
-        }
-
-        synchronized (stateLock) {
-            if (state != STATE_NEW) {
-                throw new IllegalStateException(
-                        "Could not enable/disable Channel ID after the initial handshake has"
-                                + " begun.");
-            }
-        }
-        sslParameters.channelIdEnabled = enabled;
-    }
-
-    /**
-     * Gets the TLS Channel ID for this server socket. Channel ID is only available once the
-     * handshake completes.
-     *
-     * @return channel ID or {@code null} if not available.
-     *
-     * @throws IllegalStateException if this is a client socket or if the handshake has not yet
-     *         completed.
-     * @throws SSLException if channel ID is available but could not be obtained.
-     */
-    public byte[] getChannelId() throws SSLException {
-        if (getUseClientMode()) {
-            throw new IllegalStateException("Client mode");
-        }
-
-        synchronized (stateLock) {
-            if (state != STATE_READY) {
-                throw new IllegalStateException(
-                        "Channel ID is only available after handshake completes");
-            }
-        }
-        return NativeCrypto.SSL_get_tls_channel_id(sslNativePointer);
-    }
-
-    /**
-     * Sets the {@link PrivateKey} to be used for TLS Channel ID by this client socket.
-     *
-     * <p>This method needs to be invoked before the handshake starts.
-     *
-     * @param privateKey private key (enables TLS Channel ID) or {@code null} for no key (disables
-     *        TLS Channel ID). The private key must be an Elliptic Curve (EC) key based on the NIST
-     *        P-256 curve (aka SECG secp256r1 or ANSI X9.62 prime256v1).
-     *
-     * @throws IllegalStateException if this is a server socket or if the handshake has already
-     *         started.
-     */
-    public void setChannelIdPrivateKey(PrivateKey privateKey) {
-        if (!getUseClientMode()) {
-            throw new IllegalStateException("Server mode");
-        }
-
-        synchronized (stateLock) {
-            if (state != STATE_NEW) {
-                throw new IllegalStateException(
-                        "Could not change Channel ID private key after the initial handshake has"
-                                + " begun.");
-            }
-        }
-
-        if (privateKey == null) {
-            sslParameters.channelIdEnabled = false;
-            channelIdPrivateKey = null;
-        } else {
-            sslParameters.channelIdEnabled = true;
-            try {
-                ECParameterSpec ecParams = null;
-                if (privateKey instanceof ECKey) {
-                    ecParams = ((ECKey) privateKey).getParams();
-                }
-                if (ecParams == null) {
-                    // Assume this is a P-256 key, as specified in the contract of this method.
-                    ecParams =
-                            OpenSSLECGroupContext.getCurveByName("prime256v1").getECParameterSpec();
-                }
-                channelIdPrivateKey =
-                        OpenSSLKey.fromECPrivateKeyForTLSStackOnly(privateKey, ecParams);
-            } catch (InvalidKeyException e) {
-                // Will have error in startHandshake
-            }
-        }
+        super.setHostname(hostname);
     }
 
     @Override
-    public boolean getUseClientMode() {
-        return sslParameters.getUseClientMode();
+    public String getHostnameOrIP() {
+        return super.getHostnameOrIP();
     }
 
     @Override
-    public void setUseClientMode(boolean mode) {
-        synchronized (stateLock) {
-            if (state != STATE_NEW) {
-                throw new IllegalArgumentException(
-                        "Could not change the mode after the initial handshake has begun.");
-            }
-        }
-        sslParameters.setUseClientMode(mode);
-    }
-
-    @Override
-    public boolean getWantClientAuth() {
-        return sslParameters.getWantClientAuth();
-    }
-
-    @Override
-    public boolean getNeedClientAuth() {
-        return sslParameters.getNeedClientAuth();
-    }
-
-    @Override
-    public void setNeedClientAuth(boolean need) {
-        sslParameters.setNeedClientAuth(need);
-    }
-
-    @Override
-    public void setWantClientAuth(boolean want) {
-        sslParameters.setWantClientAuth(want);
-    }
-
-    @Override
-    public void sendUrgentData(int data) throws IOException {
-        throw new SocketException("Method sendUrgentData() is not supported.");
-    }
-
-    @Override
-    public void setOOBInline(boolean on) throws SocketException {
-        throw new SocketException("Methods sendUrgentData, setOOBInline are not supported.");
-    }
-
-    @Override
-    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
-    public void setSoTimeout(int readTimeoutMilliseconds) throws SocketException {
-        if (socket != this) {
-            socket.setSoTimeout(readTimeoutMilliseconds);
-        } else {
-            super.setSoTimeout(readTimeoutMilliseconds);
-        }
-
-        this.readTimeoutMilliseconds = readTimeoutMilliseconds;
-    }
-
-    @Override
-    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
-    public int getSoTimeout() throws SocketException {
-        return readTimeoutMilliseconds;
-    }
-
-    /**
-     * Note write timeouts are not part of the javax.net.ssl.SSLSocket API
-     */
-    public void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException {
-        this.writeTimeoutMilliseconds = writeTimeoutMilliseconds;
-
-        Platform.setSocketWriteTimeout(this, writeTimeoutMilliseconds);
-    }
-
-    /**
-     * Note write timeouts are not part of the javax.net.ssl.SSLSocket API
-     */
-    public int getSoWriteTimeout() throws SocketException {
-        return writeTimeoutMilliseconds;
-    }
-
-    /**
-     * Set the handshake timeout on this socket.  This timeout is specified in
-     * milliseconds and will be used only during the handshake process.
-     */
-    public void setHandshakeTimeout(int handshakeTimeoutMilliseconds) throws SocketException {
-        this.handshakeTimeoutMilliseconds = handshakeTimeoutMilliseconds;
-    }
-
-    @Override
-    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
-    public void close() throws IOException {
-        // TODO: Close SSL sockets using a background thread so they close gracefully.
-
-        SSLInputStream sslInputStream;
-        SSLOutputStream sslOutputStream;
-
-        synchronized (stateLock) {
-            if (state == STATE_CLOSED) {
-                // close() has already been called, so do nothing and return.
-                return;
-            }
-
-            int oldState = state;
-            state = STATE_CLOSED;
-
-            if (oldState == STATE_NEW) {
-                // The handshake hasn't been started yet, so there's no OpenSSL related
-                // state to clean up. We still need to close the underlying socket if
-                // we're wrapping it and were asked to autoClose.
-                closeUnderlyingSocket();
-
-                stateLock.notifyAll();
-                return;
-            }
-
-            if (oldState != STATE_READY && oldState != STATE_READY_HANDSHAKE_CUT_THROUGH) {
-                // If we're in these states, we still haven't returned from startHandshake.
-                // We call SSL_interrupt so that we can interrupt SSL_do_handshake and then
-                // set the state to STATE_CLOSED. startHandshake will handle all cleanup
-                // after SSL_do_handshake returns, so we don't have anything to do here.
-                NativeCrypto.SSL_interrupt(sslNativePointer);
-
-                stateLock.notifyAll();
-                return;
-            }
-
-            stateLock.notifyAll();
-            // We've already returned from startHandshake, so we potentially have
-            // input and output streams to clean up.
-            sslInputStream = is;
-            sslOutputStream = os;
-        }
-
-        // Don't bother interrupting unless we have something to interrupt.
-        if (sslInputStream != null || sslOutputStream != null) {
-            NativeCrypto.SSL_interrupt(sslNativePointer);
-        }
-
-        // Wait for the input and output streams to finish any reads they have in
-        // progress. If there are no reads in progress at this point, future reads will
-        // throw because state == STATE_CLOSED
-        if (sslInputStream != null) {
-            sslInputStream.awaitPendingOps();
-        }
-        if (sslOutputStream != null) {
-            sslOutputStream.awaitPendingOps();
-        }
-
-        shutdownAndFreeSslNative();
-    }
-
-    private void shutdownAndFreeSslNative() throws IOException {
-        try {
-            Platform.blockGuardOnNetwork();
-            NativeCrypto.SSL_shutdown(sslNativePointer, Platform.getFileDescriptor(socket),
-                    this);
-        } catch (IOException ignored) {
-            /*
-            * Note that although close() can throw
-            * IOException, the RI does not throw if there
-            * is problem sending a "close notify" which
-            * can happen if the underlying socket is closed.
-            */
-        } finally {
-            free();
-            closeUnderlyingSocket();
-        }
-    }
-
-    private void closeUnderlyingSocket() throws IOException {
-        if (socket != this) {
-            if (autoClose && !socket.isClosed()) {
-                socket.close();
-            }
-        } else {
-            if (!super.isClosed()) {
-                super.close();
-            }
-        }
-    }
-
-    private void free() {
-        if (sslNativePointer == 0) {
-            return;
-        }
-        NativeCrypto.SSL_free(sslNativePointer);
-        sslNativePointer = 0;
-        Platform.closeGuardClose(guard);
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            /*
-             * Just worry about our own state. Notably we do not try and
-             * close anything. The SocketImpl, either our own
-             * PlainSocketImpl, or the Socket we are wrapping, will do
-             * that. This might mean we do not properly SSL_shutdown, but
-             * if you want to do that, properly close the socket yourself.
-             *
-             * The reason why we don't try to SSL_shutdown, is that there
-             * can be a race between finalizers where the PlainSocketImpl
-             * finalizer runs first and closes the socket. However, in the
-             * meanwhile, the underlying file descriptor could be reused
-             * for another purpose. If we call SSL_shutdown, the
-             * underlying socket BIOs still have the old file descriptor
-             * and will write the close notify to some unsuspecting
-             * reader.
-             */
-            if (guard != null) {
-                Platform.closeGuardWarnIfOpen(guard);
-            }
-            free();
-        } finally {
-            super.finalize();
-        }
-    }
-
-    /* @Override */
     public FileDescriptor getFileDescriptor$() {
-        if (socket == this) {
-            return Platform.getFileDescriptorFromSSLSocket(this);
-        } else {
-            return Platform.getFileDescriptor(socket);
-        }
-    }
-
-    /**
-     * Returns null always for backward compatibility.
-     */
-    public byte[] getNpnSelectedProtocol() {
-        return null;
-    }
-
-    /**
-     * Returns the protocol agreed upon by client and server, or {@code null} if
-     * no protocol was agreed upon.
-     */
-    public byte[] getAlpnSelectedProtocol() {
-        return NativeCrypto.SSL_get0_alpn_selected(sslNativePointer);
-    }
-
-    /**
-     * This method does nothing and is kept for backward compatibility.
-     */
-    public void setNpnProtocols(byte[] npnProtocols) {
-    }
-
-    /**
-     * Sets the list of ALPN protocols. This method internally converts the protocols to their
-     * wire-format form.
-     *
-     * @param alpnProtocols the list of ALPN protocols
-     * @see #setAlpnProtocols(byte[])
-     */
-    public void setAlpnProtocols(String[] alpnProtocols) {
-        sslParameters.setAlpnProtocols(alpnProtocols);
-    }
-
-    /**
-     * Alternate version of {@link #setAlpnProtocols(String[])} that directly sets the list of
-     * ALPN in the wire-format form used by BoringSSL (length-prefixed 8-bit strings).
-     * Requires that all strings be encoded with US-ASCII.
-     *
-     * @param alpnProtocols the encoded form of the ALPN protocol list
-     * @see #setAlpnProtocols(String[])
-     */
-    public void setAlpnProtocols(byte[] alpnProtocols) {
-        sslParameters.setAlpnProtocols(alpnProtocols);
+        return super.getFileDescriptor$();
     }
 
     @Override
-    public SSLParameters getSSLParameters() {
-        SSLParameters params = super.getSSLParameters();
-        Platform.getSSLParameters(params, sslParameters, this);
-        return params;
+    public void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException {
+        super.setSoWriteTimeout(writeTimeoutMilliseconds);
     }
 
     @Override
-    public void setSSLParameters(SSLParameters p) {
-        super.setSSLParameters(p);
-        Platform.setSSLParameters(p, sslParameters, this);
+    public int getSoWriteTimeout() throws SocketException {
+        return super.getSoWriteTimeout();
     }
 
     @Override
-    public String chooseServerAlias(X509KeyManager keyManager, String keyType) {
-        return keyManager.chooseServerAlias(keyType, null, this);
+    public void setHandshakeTimeout(int handshakeTimeoutMilliseconds) throws SocketException {
+        super.setHandshakeTimeout(handshakeTimeoutMilliseconds);
     }
 
     @Override
-    public String chooseClientAlias(X509KeyManager keyManager, X500Principal[] issuers,
-            String[] keyTypes) {
-        return keyManager.chooseClientAlias(keyTypes, null, this);
+    public abstract SSLSession getHandshakeSession();
+
+    @Override
+    public abstract void setUseSessionTickets(boolean useSessionTickets);
+
+    @Override
+    public abstract void setChannelIdEnabled(boolean enabled);
+
+    @Override
+    public abstract byte[] getChannelId() throws SSLException;
+
+    @Override
+    public abstract void setChannelIdPrivateKey(PrivateKey privateKey);
+
+    @Override
+    public final byte[] getNpnSelectedProtocol() {
+        return super.getNpnSelectedProtocol();
     }
 
     @Override
-    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
-    public String chooseServerPSKIdentityHint(PSKKeyManager keyManager) {
-        return keyManager.chooseServerKeyIdentityHint(this);
+    public final void setNpnProtocols(byte[] npnProtocols) {
+        super.setNpnProtocols(npnProtocols);
     }
 
     @Override
-    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
-    public String chooseClientPSKIdentity(PSKKeyManager keyManager, String identityHint) {
-        return keyManager.chooseClientKeyIdentity(identityHint, this);
-    }
+    public abstract byte[] getAlpnSelectedProtocol();
 
     @Override
-    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
-    public SecretKey getPSKKey(PSKKeyManager keyManager, String identityHint, String identity) {
-        return keyManager.getKey(identityHint, identity, this);
-    }
+    public abstract void setAlpnProtocols(String[] alpnProtocols);
+
+    @Override
+    public abstract void setAlpnProtocols(byte[] alpnProtocols);
 }
diff --git a/common/src/main/java/org/conscrypt/OpenSSLSocketImplWrapper.java b/common/src/main/java/org/conscrypt/OpenSSLSocketImplWrapper.java
deleted file mode 100644
index 848ef7f..0000000
--- a/common/src/main/java/org/conscrypt/OpenSSLSocketImplWrapper.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF 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;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.net.SocketException;
-
-/**
- * This class wraps the SSL functionality over an existing connected socket.
- */
-class OpenSSLSocketImplWrapper extends OpenSSLSocketImpl {
-
-    private Socket socket;
-
-    OpenSSLSocketImplWrapper(Socket socket, String hostname, int port,
-            boolean autoClose, SSLParametersImpl sslParameters) throws IOException {
-        super(socket, hostname, port, autoClose, sslParameters);
-        if (!socket.isConnected()) {
-            throw new SocketException("Socket is not connected.");
-        }
-        this.socket = socket;
-    }
-
-    @Override
-    public void connect(SocketAddress sockaddr, int timeout)
-        throws IOException {
-        socket.connect(sockaddr, timeout);
-    }
-
-    @Override
-    public void connect(SocketAddress sockaddr) throws IOException {
-        socket.connect(sockaddr);
-    }
-
-    @Override
-    public void bind(SocketAddress sockaddr) throws IOException {
-        socket.bind(sockaddr);
-    }
-
-    @Override
-    public SocketAddress getRemoteSocketAddress() {
-        return socket.getRemoteSocketAddress();
-    }
-
-    @Override
-    public SocketAddress getLocalSocketAddress() {
-        return socket.getLocalSocketAddress();
-    }
-
-    @Override
-    public InetAddress getLocalAddress() {
-        return socket.getLocalAddress();
-    }
-
-    @Override
-    public InetAddress getInetAddress() {
-        return socket.getInetAddress();
-    }
-
-    @Override
-    public String toString() {
-        return "SSL socket over " + socket.toString();
-    }
-
-    @Override
-    public void setSoLinger(boolean on, int linger) throws SocketException {
-        socket.setSoLinger(on, linger);
-    }
-
-    @Override
-    public void setTcpNoDelay(boolean on) throws SocketException {
-        socket.setTcpNoDelay(on);
-    }
-
-    @Override
-    public void setReuseAddress(boolean on) throws SocketException {
-        socket.setReuseAddress(on);
-    }
-
-    @Override
-    public void setKeepAlive(boolean on) throws SocketException {
-        socket.setKeepAlive(on);
-    }
-
-    @Override
-    public void setTrafficClass(int tos) throws SocketException {
-        socket.setTrafficClass(tos);
-    }
-
-    @Override
-    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
-    public void setSendBufferSize(int size) throws SocketException {
-        socket.setSendBufferSize(size);
-    }
-
-    @Override
-    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
-    public void setReceiveBufferSize(int size) throws SocketException {
-        socket.setReceiveBufferSize(size);
-    }
-
-    @Override
-    public boolean getTcpNoDelay() throws SocketException {
-        return socket.getTcpNoDelay();
-    }
-
-    @Override
-    public boolean getReuseAddress() throws SocketException {
-        return socket.getReuseAddress();
-    }
-
-    @Override
-    public boolean getOOBInline() throws SocketException {
-        return socket.getOOBInline();
-    }
-
-    @Override
-    public boolean getKeepAlive() throws SocketException {
-        return socket.getKeepAlive();
-    }
-
-    @Override
-    public int getTrafficClass() throws SocketException {
-        return socket.getTrafficClass();
-    }
-
-    @Override
-    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
-    public int getSoTimeout() throws SocketException {
-        return socket.getSoTimeout();
-    }
-
-    @Override
-    public int getSoLinger() throws SocketException {
-        return socket.getSoLinger();
-    }
-
-    @Override
-    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
-    public int getSendBufferSize() throws SocketException {
-        return socket.getSendBufferSize();
-    }
-
-    @Override
-    @SuppressWarnings("UnsynchronizedOverridesSynchronized")
-    public int getReceiveBufferSize() throws SocketException {
-        return socket.getReceiveBufferSize();
-    }
-
-    @Override
-    public boolean isConnected() {
-        return socket.isConnected();
-    }
-
-    @Override
-    public boolean isClosed() {
-        return socket.isClosed();
-    }
-
-    @Override
-    public boolean isBound() {
-        return socket.isBound();
-    }
-
-    @Override
-    public boolean isOutputShutdown() {
-        return socket.isOutputShutdown();
-    }
-
-    @Override
-    public boolean isInputShutdown() {
-        return socket.isInputShutdown();
-    }
-
-    @Override
-    public int getPort() {
-        return socket.getPort();
-    }
-
-    @Override
-    public int getLocalPort() {
-        return socket.getLocalPort();
-    }
-}
diff --git a/common/src/main/java/org/conscrypt/PeerInfoProvider.java b/common/src/main/java/org/conscrypt/PeerInfoProvider.java
index dfa3fc7..e70f0cd 100644
--- a/common/src/main/java/org/conscrypt/PeerInfoProvider.java
+++ b/common/src/main/java/org/conscrypt/PeerInfoProvider.java
@@ -21,6 +21,11 @@
 abstract class PeerInfoProvider {
     private static final PeerInfoProvider NULL_PEER_INFO_PROVIDER = new PeerInfoProvider() {
         @Override
+        String getHostname() {
+            return null;
+        }
+
+        @Override
         public String getHostnameOrIP() {
             return null;
         }
@@ -32,6 +37,12 @@
     };
 
     /**
+     * Returns the hostname supplied during engine/socket creation. No DNS resolution is
+     * attempted before returning the hostname.
+     */
+    abstract String getHostname();
+
+    /**
      * This method attempts to create a textual representation of the peer host or IP. Does
      * not perform a reverse DNS lookup. This is typically used during session creation.
      */
@@ -49,6 +60,11 @@
     static PeerInfoProvider forHostAndPort(final String host, final int port) {
         return new PeerInfoProvider() {
             @Override
+            String getHostname() {
+                return host;
+            }
+
+            @Override
             public String getHostnameOrIP() {
                 return host;
             }
diff --git a/common/src/main/java/org/conscrypt/SSLUtils.java b/common/src/main/java/org/conscrypt/SSLUtils.java
index efdf134..74e6cc5 100644
--- a/common/src/main/java/org/conscrypt/SSLUtils.java
+++ b/common/src/main/java/org/conscrypt/SSLUtils.java
@@ -50,8 +50,8 @@
  * This is a public class to allow testing to occur on Android via CTS.
  */
 final class SSLUtils {
-    static final boolean USE_ENGINE_SOCKET_BY_DEFAULT =
-            Boolean.parseBoolean(System.getProperty("org.conscrypt.useEngineSocketByDefault"));
+    static final boolean USE_ENGINE_SOCKET_BY_DEFAULT = Boolean.parseBoolean(
+            System.getProperty("org.conscrypt.useEngineSocketByDefault", "false"));
     private static final int MAX_PROTOCOL_LENGTH = 255;
 
     /**
diff --git a/openjdk-integ-tests/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java b/openjdk-integ-tests/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java
index 7c51c20..776a19dd 100644
--- a/openjdk-integ-tests/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java
+++ b/openjdk-integ-tests/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java
@@ -676,27 +676,38 @@
         server.close();
         c.close();
     }
+
     @Test
-    public void test_SSLSocket_setUseClientMode() throws Exception {
-        // client is client, server is server
+    public void testClientMode_normal() throws Exception {
+        // Client is client and server is server.
         test_SSLSocket_setUseClientMode(true, false);
-        // client is server, server is client
-        test_SSLSocket_setUseClientMode(true, false);
-        // both are client
-        try {
-            test_SSLSocket_setUseClientMode(true, true);
-            fail();
-        } catch (SSLProtocolException | SSLHandshakeException expected) {
-            // Ignored.
-        }
-        // both are server
+    }
+
+    @Test(expected = SSLHandshakeException.class)
+    public void testClientMode_reverse() throws Exception {
+        // Client is server and server is client.
+        test_SSLSocket_setUseClientMode(false, true);
+    }
+
+    @Test(expected = SSLHandshakeException.class)
+    public void testClientMode_bothClient() throws Exception {
+        test_SSLSocket_setUseClientMode(true, true);
+    }
+
+    @Test
+    public void testClientMode_bothServer() throws Exception {
         try {
             test_SSLSocket_setUseClientMode(false, false);
             fail();
         } catch (SocketTimeoutException expected) {
-            // Ignored.
+            // Ignore
+        } catch (SSLHandshakeException expected) {
+            // Depending on the timing of the socket closures, this can happen as well.
+            assertTrue("Unexpected handshake error: " + expected.getMessage(),
+                    expected.getMessage().toLowerCase().contains("connection closed"));
         }
     }
+
     private void test_SSLSocket_setUseClientMode(
             final boolean clientClientMode, final boolean serverClientMode) throws Exception {
         TestSSLContext c = TestSSLContext.create();
@@ -1595,13 +1606,13 @@
         }
     }
 
-    // TODO(nmittler): FD socket read may return -1 instead of SocketException.
+    // TODO(nmittler): Conscrypt socket read may return -1 instead of SocketException.
     @Test
     public void test_SSLSocket_interrupt_readUnderlyingAndCloseUnderlying() throws Exception {
         test_SSLSocket_interrupt_case(true, true);
     }
 
-    // TODO(nmittler): FD socket read may return -1 instead of SocketException.
+    // TODO(nmittler): Conscrypt socket read may return -1 instead of SocketException.
     @Test
     public void test_SSLSocket_interrupt_readUnderlyingAndCloseWrapper() throws Exception {
         test_SSLSocket_interrupt_case(true, false);
@@ -1613,7 +1624,7 @@
         test_SSLSocket_interrupt_case(false, true);
     }
 
-    // TODO(nmittler): FD socket read may return -1 instead of SocketException.
+    // TODO(nmittler): Conscrypt socket read may return -1 instead of SocketException.
     @Test
     public void test_SSLSocket_interrupt_readWrapperAndCloseWrapper() throws Exception {
         test_SSLSocket_interrupt_case(false, false);
@@ -1660,11 +1671,11 @@
             toRead.setSoTimeout(readingTimeoutMillis);
             final InputStream inputStream = toRead.getInputStream();
             int value = inputStream.read();
-            if (isConscryptFdSocket(clientWrapping)) {
-                // TODO(nmittler): FD socket read may return -1 instead of SocketException.
+            if (isConscryptSocket(clientWrapping)) {
+                // TODO(nmittler): Conscrypt socket read may return -1 instead of SocketException.
                 assertEquals(-1, value);
             } else {
-                // For every other condition, we should expect SocketException.
+                // For any other socket type, we should expect SocketException.
                 fail();
             }
         } catch (SocketException e) {
@@ -1682,29 +1693,33 @@
      * thread will interrupt another thread blocked reading on the same
      * socket.
      */
+    // TODO(nmittler): Interrupts do not work with the engine-based socket.
     @Test
-    public void test_SSLSocket_interrupt_read() throws Exception {
+    public void test_SSLSocket_interrupt_read_withoutAutoClose() throws Exception {
         final int readingTimeoutMillis = 5000;
         TestSSLContext c = TestSSLContext.create();
         final Socket underlying = new Socket(c.host, c.port);
         final SSLSocket wrapping = (SSLSocket) c.clientContext.getSocketFactory().createSocket(
                 underlying, c.host.getHostName(), c.port, false);
+
+        // TODO(nmittler): Interrupts do not work with the engine-based socket.
+        assumeFalse(isConscryptEngineSocket(wrapping));
+
         Future<Void> clientFuture = runAsync(() -> {
             wrapping.startHandshake();
             try {
                 wrapping.setSoTimeout(readingTimeoutMillis);
                 int ret = wrapping.getInputStream().read();
                 // Android returns -1 rather than throwing.
-                if (isConscryptFdSocket(wrapping)) {
-                    // This seems to only happen with Conscrypt's FD-based socket.
+                if (isConscryptSocket(wrapping)) {
+                    // TODO(nmittler): Conscrypt socket read may return -1 instead of
+                    // SocketException.
                     assertEquals(-1, ret);
                 } else {
-                    // For every other condition, we should expect SocketException.
+                    // For any other socket type, we should expect SocketException.
                     fail();
                 }
             } catch (SocketException e) {
-                // Opposite condition of the one above. Verify the behavior we expect.
-                assertFalse(isConscryptFdSocket(wrapping));
                 // Otherwise, ignore the exception since it's expected.
             }
             return null;
@@ -1733,6 +1748,7 @@
         }
 
         wrapping.close();
+
         clientFuture.get();
         server.close();
     }
@@ -2277,8 +2293,16 @@
         }
     }
 
+    private static boolean isConscryptSocket(Socket socket) {
+        return isConscryptFdSocket(socket) || isConscryptEngineSocket(socket);
+    }
+
     private static boolean isConscryptFdSocket(Socket socket) {
-        return "OpenSSLSocketImplWrapper".equals(socket.getClass().getSimpleName());
+        return "ConscryptFileDescriptorSocket".equals(socket.getClass().getSimpleName());
+    }
+
+    private static boolean isConscryptEngineSocket(Socket socket) {
+        return "ConscryptEngineSocket".equals(socket.getClass().getSimpleName());
     }
 
     private static String osName() {
diff --git a/openjdk/src/main/java/org/conscrypt/Platform.java b/openjdk/src/main/java/org/conscrypt/Platform.java
index 0da249c..7b5adb6 100644
--- a/openjdk/src/main/java/org/conscrypt/Platform.java
+++ b/openjdk/src/main/java/org/conscrypt/Platform.java
@@ -87,8 +87,8 @@
         }
     }
 
-    static FileDescriptor getFileDescriptorFromSSLSocket(OpenSSLSocketImpl openSSLSocketImpl) {
-        return getFileDescriptor(openSSLSocketImpl);
+    static FileDescriptor getFileDescriptorFromSSLSocket(AbstractConscryptSocket socket) {
+        return getFileDescriptor(socket);
     }
 
     static String getCurveName(ECParameterSpec spec) {
@@ -117,7 +117,7 @@
 
     @SuppressWarnings("unchecked")
     public static void setSSLParameters(
-            SSLParameters params, SSLParametersImpl impl, OpenSSLSocketImpl socket) {
+            SSLParameters params, SSLParametersImpl impl, AbstractConscryptSocket socket) {
         impl.setEndpointIdentificationAlgorithm(params.getEndpointIdentificationAlgorithm());
         try {
             Method getUseCipherSuitesOrder =
@@ -147,7 +147,7 @@
 
     @SuppressWarnings({"LiteralClassName", "rawtypes"})
     public static void getSSLParameters(
-            SSLParameters params, SSLParametersImpl impl, OpenSSLSocketImpl socket) {
+            SSLParameters params, SSLParametersImpl impl, AbstractConscryptSocket socket) {
         params.setEndpointIdentificationAlgorithm(impl.getEndpointIdentificationAlgorithm());
         try {
             Method setUseCipherSuitesOrder =
@@ -234,7 +234,7 @@
     }
 
     static void checkClientTrusted(X509TrustManager tm, X509Certificate[] chain, String authType,
-            OpenSSLSocketImpl socket) throws CertificateException {
+            AbstractConscryptSocket socket) throws CertificateException {
         if (tm instanceof X509ExtendedTrustManager) {
             X509ExtendedTrustManager x509etm = (X509ExtendedTrustManager) tm;
             x509etm.checkClientTrusted(chain, authType, socket);
@@ -244,7 +244,7 @@
     }
 
     static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain, String authType,
-            OpenSSLSocketImpl socket) throws CertificateException {
+            AbstractConscryptSocket socket) throws CertificateException {
         if (tm instanceof X509ExtendedTrustManager) {
             X509ExtendedTrustManager x509etm = (X509ExtendedTrustManager) tm;
             x509etm.checkServerTrusted(chain, authType, socket);
diff --git a/openjdk/src/test/java/org/conscrypt/OpenSSLServerSocketImplTest.java b/openjdk/src/test/java/org/conscrypt/ConscryptServerSocketTest.java
similarity index 98%
rename from openjdk/src/test/java/org/conscrypt/OpenSSLServerSocketImplTest.java
rename to openjdk/src/test/java/org/conscrypt/ConscryptServerSocketTest.java
index d84b72a..419469c 100644
--- a/openjdk/src/test/java/org/conscrypt/OpenSSLServerSocketImplTest.java
+++ b/openjdk/src/test/java/org/conscrypt/ConscryptServerSocketTest.java
@@ -41,7 +41,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class OpenSSLServerSocketImplTest {
+public class ConscryptServerSocketTest {
     private static final String CIPHER = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256";
     private static final int MESSAGE_SIZE = 4096;
 
diff --git a/openjdk/src/test/java/org/conscrypt/OpenSSLSocketImplTest.java b/openjdk/src/test/java/org/conscrypt/OpenSSLSocketImplTest.java
index 8c59e8f..249878c 100644
--- a/openjdk/src/test/java/org/conscrypt/OpenSSLSocketImplTest.java
+++ b/openjdk/src/test/java/org/conscrypt/OpenSSLSocketImplTest.java
@@ -77,7 +77,7 @@
             @Override
             void assertSocketType(Socket socket) {
                 assertTrue("Unexpected socket type: " + socket.getClass().getName(),
-                        socket instanceof OpenSSLEngineSocketImpl);
+                        socket instanceof ConscryptEngineSocket);
             }
         };
 
@@ -87,23 +87,23 @@
             this.useEngineSocket = useEngineSocket;
         }
 
-        OpenSSLSocketImpl createClientSocket(OpenSSLContextImpl context, ServerSocket listener)
-                throws IOException {
+        AbstractConscryptSocket createClientSocket(
+                OpenSSLContextImpl context, ServerSocket listener) throws IOException {
             SSLSocketFactory factory = context.engineGetSocketFactory();
             Conscrypt.SocketFactories.setUseEngineSocket(factory, useEngineSocket);
-            OpenSSLSocketImpl socket = (OpenSSLSocketImpl) factory.createSocket(
+            AbstractConscryptSocket socket = (AbstractConscryptSocket) factory.createSocket(
                     listener.getInetAddress(), listener.getLocalPort());
             assertSocketType(socket);
             socket.setUseClientMode(true);
             return socket;
         }
 
-        OpenSSLSocketImpl createServerSocket(OpenSSLContextImpl context, ServerSocket listener)
-                throws IOException {
+        AbstractConscryptSocket createServerSocket(
+                OpenSSLContextImpl context, ServerSocket listener) throws IOException {
             SSLSocketFactory factory = context.engineGetSocketFactory();
             Conscrypt.SocketFactories.setUseEngineSocket(factory, useEngineSocket);
-            OpenSSLSocketImpl socket = (OpenSSLSocketImpl) factory.createSocket(listener.accept(),
-                    null, -1, // hostname, port
+            AbstractConscryptSocket socket = (AbstractConscryptSocket) factory.createSocket(
+                    listener.accept(), null, -1, // hostname, port
                     true); // autoclose
             assertSocketType(socket);
             socket.setUseClientMode(false);
@@ -152,7 +152,7 @@
         KeyManager[] keyManagers;
         TrustManager[] trustManagers;
 
-        abstract OpenSSLSocketImpl createSocket(ServerSocket listener) throws IOException;
+        abstract AbstractConscryptSocket createSocket(ServerSocket listener) throws IOException;
 
         OpenSSLContextImpl createContext() throws IOException {
             OpenSSLContextImpl context = OpenSSLContextImpl.getPreferred();
@@ -192,8 +192,9 @@
         }
 
         @Override
-        OpenSSLSocketImpl createSocket(ServerSocket listener) throws IOException {
-            OpenSSLSocketImpl socket = socketType.createClientSocket(createContext(), listener);
+        AbstractConscryptSocket createSocket(ServerSocket listener) throws IOException {
+            AbstractConscryptSocket socket =
+                    socketType.createClientSocket(createContext(), listener);
             socket.setHostname(hostname);
             return socket;
         }
@@ -217,7 +218,7 @@
         }
 
         @Override
-        OpenSSLSocketImpl createSocket(ServerSocket listener) throws IOException {
+        AbstractConscryptSocket createSocket(ServerSocket listener) throws IOException {
             return socketType.createServerSocket(createContext(), listener);
         }
     }
@@ -226,8 +227,8 @@
         ServerHooks serverHooks;
         ClientHooks clientHooks;
 
-        OpenSSLSocketImpl client;
-        OpenSSLSocketImpl server;
+        AbstractConscryptSocket client;
+        AbstractConscryptSocket server;
 
         Exception clientException;
         Exception serverException;
@@ -274,8 +275,8 @@
 
         void doHandshake() throws Exception {
             ServerSocket listener = newServerSocket();
-            Future<OpenSSLSocketImpl> clientFuture = handshake(listener, clientHooks);
-            Future<OpenSSLSocketImpl> serverFuture = handshake(listener, serverHooks);
+            Future<AbstractConscryptSocket> clientFuture = handshake(listener, clientHooks);
+            Future<AbstractConscryptSocket> serverFuture = handshake(listener, serverHooks);
 
             try {
                 client = getOrThrowCause(clientFuture, TIMEOUT_SECONDS, TimeUnit.SECONDS);
@@ -289,9 +290,9 @@
             }
         }
 
-        Future<OpenSSLSocketImpl> handshake(final ServerSocket listener, final Hooks hooks) {
+        Future<AbstractConscryptSocket> handshake(final ServerSocket listener, final Hooks hooks) {
             return executor.submit(() -> {
-                OpenSSLSocketImpl socket = hooks.createSocket(listener);
+                AbstractConscryptSocket socket = hooks.createSocket(listener);
                 socket.addHandshakeCompletedListener(hooks);
 
                 socket.startHandshake();
@@ -390,8 +391,8 @@
 
         connection.clientHooks = new ClientHooks() {
             @Override
-            public OpenSSLSocketImpl createSocket(ServerSocket listener) throws IOException {
-                OpenSSLSocketImpl socket = super.createSocket(listener);
+            public AbstractConscryptSocket createSocket(ServerSocket listener) throws IOException {
+                AbstractConscryptSocket socket = super.createSocket(listener);
                 socket.setEnabledProtocols(new String[] {"SSLv3"});
                 assertEquals(
                         "SSLv3 should be filtered out", 0, socket.getEnabledProtocols().length);
diff --git a/openjdk/src/test/java/org/conscrypt/PlatformTest.java b/openjdk/src/test/java/org/conscrypt/PlatformTest.java
index 2afd99d..1d7ea75 100644
--- a/openjdk/src/test/java/org/conscrypt/PlatformTest.java
+++ b/openjdk/src/test/java/org/conscrypt/PlatformTest.java
@@ -37,8 +37,8 @@
         params.setServerNames(names);
         params.setUseCipherSuitesOrder(false);
         params.setEndpointIdentificationAlgorithm("ABC");
-        Platform.setSSLParameters(params, impl, (OpenSSLSocketImpl)socket);
-        assertEquals("some.host", ((OpenSSLSocketImpl)socket).getHostname());
+        Platform.setSSLParameters(params, impl, (AbstractConscryptSocket)socket);
+        assertEquals("some.host", ((AbstractConscryptSocket)socket).getHostname());
         assertFalse(impl.getUseCipherSuitesOrder());
         assertEquals("ABC", impl.getEndpointIdentificationAlgorithm());
     }
@@ -49,8 +49,8 @@
         SSLParameters params = new SSLParameters();
         impl.setUseCipherSuitesOrder(false);
         impl.setEndpointIdentificationAlgorithm("ABC");
-        ((OpenSSLSocketImpl)socket).setHostname("some.host");
-        Platform.getSSLParameters(params, impl, (OpenSSLSocketImpl)socket);
+        ((AbstractConscryptSocket)socket).setHostname("some.host");
+        Platform.getSSLParameters(params, impl, (AbstractConscryptSocket)socket);
         assertEquals("some.host", ((SNIHostName)params.getServerNames().get(0)).getAsciiName());
         assertFalse(params.getUseCipherSuitesOrder());
         assertEquals("ABC", params.getEndpointIdentificationAlgorithm());
diff --git a/platform/proguard-rules.pro b/platform/proguard-rules.pro
index c3bdd2c..3bc75b2 100644
--- a/platform/proguard-rules.pro
+++ b/platform/proguard-rules.pro
@@ -20,5 +20,7 @@
 -dontwarn dalvik.system.BlockGuard
 -dontwarn dalvik.system.BlockGuard$Policy
 -dontwarn dalvik.system.CloseGuard
+-dontwarn com.android.org.conscrypt.AbstractConscryptSocket
+-dontwarn com.android.org.conscrypt.ConscryptFileDescriptorSocket
 -dontwarn com.android.org.conscrypt.OpenSSLSocketImpl
 -dontwarn org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl
diff --git a/platform/src/main/java/org/conscrypt/Platform.java b/platform/src/main/java/org/conscrypt/Platform.java
index 6a74a03..5a62468 100644
--- a/platform/src/main/java/org/conscrypt/Platform.java
+++ b/platform/src/main/java/org/conscrypt/Platform.java
@@ -75,11 +75,11 @@
         return s.getFileDescriptor$();
     }
 
-    static FileDescriptor getFileDescriptorFromSSLSocket(OpenSSLSocketImpl openSSLSocketImpl) {
+    static FileDescriptor getFileDescriptorFromSSLSocket(AbstractConscryptSocket socket) {
         try {
             Field f_impl = Socket.class.getDeclaredField("impl");
             f_impl.setAccessible(true);
-            Object socketImpl = f_impl.get(openSSLSocketImpl);
+            Object socketImpl = f_impl.get(socket);
             Field f_fd = SocketImpl.class.getDeclaredField("fd");
             f_fd.setAccessible(true);
             return (FileDescriptor) f_fd.get(socketImpl);
@@ -106,7 +106,7 @@
     }
 
     static void setSSLParameters(
-            SSLParameters params, SSLParametersImpl impl, OpenSSLSocketImpl socket) {
+            SSLParameters params, SSLParametersImpl impl, AbstractConscryptSocket socket) {
         impl.setEndpointIdentificationAlgorithm(params.getEndpointIdentificationAlgorithm());
         impl.setUseCipherSuitesOrder(params.getUseCipherSuitesOrder());
         List<SNIServerName> serverNames = params.getServerNames();
@@ -121,7 +121,7 @@
     }
 
     static void getSSLParameters(
-            SSLParameters params, SSLParametersImpl impl, OpenSSLSocketImpl socket) {
+            SSLParameters params, SSLParametersImpl impl, AbstractConscryptSocket socket) {
         params.setEndpointIdentificationAlgorithm(impl.getEndpointIdentificationAlgorithm());
         params.setUseCipherSuitesOrder(impl.getUseCipherSuitesOrder());
         if (impl.getUseSni() && AddressUtils.isValidSniHostname(socket.getHostname())) {
@@ -171,7 +171,7 @@
         } catch (NoSuchMethodException | IllegalAccessException ignored) {
         } catch (InvocationTargetException e) {
             if (e.getCause() instanceof CertificateException) {
-                throw (CertificateException) e.getCause();
+                throw(CertificateException) e.getCause();
             }
             throw new RuntimeException(e.getCause());
         }
@@ -179,7 +179,7 @@
     }
 
     static void checkClientTrusted(X509TrustManager tm, X509Certificate[] chain, String authType,
-            OpenSSLSocketImpl socket) throws CertificateException {
+            AbstractConscryptSocket socket) throws CertificateException {
         if (tm instanceof X509ExtendedTrustManager) {
             X509ExtendedTrustManager x509etm = (X509ExtendedTrustManager) tm;
             x509etm.checkClientTrusted(chain, authType, socket);
@@ -191,7 +191,7 @@
     }
 
     static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain, String authType,
-            OpenSSLSocketImpl socket) throws CertificateException {
+            AbstractConscryptSocket socket) throws CertificateException {
         if (tm instanceof X509ExtendedTrustManager) {
             X509ExtendedTrustManager x509etm = (X509ExtendedTrustManager) tm;
             x509etm.checkServerTrusted(chain, authType, socket);