Refactoring session management (#172)

This change breaks session management into two distinct types:

- SslSessionWrapper: These are created as BoringSSL calls back the new session handler, allowing the application to cache sessions. Clients will also offer these to BoringSSL for reuse if a compatible session was found. BoringSSL is free to use it or not, but the Conscrypt code no longer makes assumptions here. Instead, it always uses the ActiveSession.

- ActiveSession: This is a session that wraps the SSL instance (rather than the SSL_SESSION wherever possible). That way no concern has to be paid to what BoringSSL is doing with sessions under the covers.

Fixes #98
diff --git a/android/lint.xml b/android/lint.xml
index d899843..0f057a9 100644
--- a/android/lint.xml
+++ b/android/lint.xml
@@ -2,7 +2,7 @@
 <lint>
     <!-- ExtendedSSLSession only gets instantiated in new APIs on Android. -->
     <issue id="NewApi">
-        <ignore path="**/org/conscrypt/OpenSSLExtendedSessionImpl.java" />
+        <ignore path="**/org/conscrypt/DelegatingExtendedSSLSession.java" />
     </issue>
 
     <!-- Android SparseArrays can't be used in common directory. -->
diff --git a/android/src/main/java/org/conscrypt/Platform.java b/android/src/main/java/org/conscrypt/Platform.java
index be8eba6..32b4b6d 100644
--- a/android/src/main/java/org/conscrypt/Platform.java
+++ b/android/src/main/java/org/conscrypt/Platform.java
@@ -696,12 +696,12 @@
      * Pre-Java 8 backward compatibility.
      */
 
-    public static SSLSession wrapSSLSession(AbstractOpenSSLSession sslSession) {
+    public static SSLSession wrapSSLSession(ActiveSession sslSession) {
         if (Build.VERSION.SDK_INT <= 23) {
             return sslSession;
         }
 
-        return new OpenSSLExtendedSessionImpl(sslSession);
+        return new DelegatingExtendedSSLSession(sslSession);
     }
 
     public static SSLSession unwrapSSLSession(SSLSession sslSession) {
@@ -709,9 +709,10 @@
             return sslSession;
         }
 
-        if (sslSession instanceof OpenSSLExtendedSessionImpl) {
-            return ((OpenSSLExtendedSessionImpl) sslSession).getDelegate();
+        if (sslSession instanceof DelegatingExtendedSSLSession) {
+            return ((DelegatingExtendedSSLSession) sslSession).getDelegate();
         }
+
         return sslSession;
     }
 
diff --git a/common/src/jni/main/cpp/NativeCrypto.cpp b/common/src/jni/main/cpp/NativeCrypto.cpp
index 97aa036..02f21ee 100644
--- a/common/src/jni/main/cpp/NativeCrypto.cpp
+++ b/common/src/jni/main/cpp/NativeCrypto.cpp
@@ -981,22 +981,6 @@
     return result;
 }
 
-/**
- * private static native int EVP_PKEY_size(int pkey);
- */
-static int NativeCrypto_EVP_PKEY_size(JNIEnv* env, jclass, jobject pkeyRef) {
-    EVP_PKEY* pkey = fromContextObject<EVP_PKEY>(env, pkeyRef);
-    JNI_TRACE("EVP_PKEY_size(%p)", pkey);
-
-    if (pkey == nullptr) {
-        return -1;
-    }
-
-    int result = EVP_PKEY_size(pkey);
-    JNI_TRACE("EVP_PKEY_size(%p) => %d", pkey, result);
-    return result;
-}
-
 typedef int print_func(BIO*, const EVP_PKEY*, int, ASN1_PCTX*);
 
 static jstring evp_print_func(JNIEnv* env, jobject pkeyRef, print_func* func,
@@ -2366,23 +2350,6 @@
     return result;
 }
 
-/*
- * public static int void EVP_MD_block_size(long)
- */
-static jint NativeCrypto_EVP_MD_block_size(JNIEnv* env, jclass, jlong evpMdRef) {
-    EVP_MD* evp_md = reinterpret_cast<EVP_MD*>(evpMdRef);
-    JNI_TRACE("NativeCrypto_EVP_MD_block_size(%p)", evp_md);
-
-    if (evp_md == nullptr) {
-        Errors::jniThrowNullPointerException(env, nullptr);
-        return -1;
-    }
-
-    jint result = static_cast<jint>(EVP_MD_block_size(evp_md));
-    JNI_TRACE("NativeCrypto_EVP_MD_block_size(%p) => %d", evp_md, result);
-    return result;
-}
-
 static jlong evpDigestSignVerifyInit(
         JNIEnv* env,
         int (*init_func)(EVP_MD_CTX*, EVP_PKEY_CTX**, const EVP_MD*, ENGINE*, EVP_PKEY*),
@@ -3210,18 +3177,6 @@
     return nonceLength;
 }
 
-static jint NativeCrypto_EVP_AEAD_max_tag_len(JNIEnv* env, jclass, jlong evpAeadRef) {
-    const EVP_AEAD* evpAead = reinterpret_cast<const EVP_AEAD*>(evpAeadRef);
-    JNI_TRACE("EVP_AEAD_max_tag_len(%p)", evpAead);
-    if (evpAead == nullptr) {
-        Errors::jniThrowNullPointerException(env, "evpAead == null");
-        return 0;
-    }
-    jint maxTagLen = static_cast<jint>(EVP_AEAD_max_tag_len(evpAead));
-    JNI_TRACE("EVP_AEAD_max_tag_len(%p) => %d", evpAead, maxTagLen);
-    return maxTagLen;
-}
-
 typedef int (*evp_aead_ctx_op_func)(const EVP_AEAD_CTX *ctx, uint8_t *out,
                                     size_t *out_len, size_t max_out_len,
                                     const uint8_t *nonce, size_t nonce_len,
@@ -3471,41 +3426,6 @@
     JNI_TRACE("NativeCrypto_RAND_bytes(%p) => success", output);
 }
 
-static jint NativeCrypto_OBJ_txt2nid(JNIEnv* env, jclass, jstring oidStr) {
-    JNI_TRACE("OBJ_txt2nid(%p)", oidStr);
-
-    ScopedUtfChars oid(env, oidStr);
-    if (oid.c_str() == nullptr) {
-        return 0;
-    }
-
-    int nid = OBJ_txt2nid(oid.c_str());
-    JNI_TRACE("OBJ_txt2nid(%s) => %d", oid.c_str(), nid);
-    return nid;
-}
-
-static jstring NativeCrypto_OBJ_txt2nid_longName(JNIEnv* env, jclass, jstring oidStr) {
-    JNI_TRACE("OBJ_txt2nid_longName(%p)", oidStr);
-
-    ScopedUtfChars oid(env, oidStr);
-    if (oid.c_str() == nullptr) {
-        return nullptr;
-    }
-
-    JNI_TRACE("OBJ_txt2nid_longName(%s)", oid.c_str());
-
-    int nid = OBJ_txt2nid(oid.c_str());
-    if (nid == NID_undef) {
-        JNI_TRACE("OBJ_txt2nid_longName(%s) => NID_undef", oid.c_str());
-        ERR_clear_error();
-        return nullptr;
-    }
-
-    const char* longName = OBJ_nid2ln(nid);
-    JNI_TRACE("OBJ_txt2nid_longName(%s) => %s", oid.c_str(), longName);
-    return env->NewStringUTF(longName);
-}
-
 static jstring ASN1_OBJECT_to_OID_string(JNIEnv* env, const ASN1_OBJECT* obj) {
     /*
      * The OBJ_obj2txt API doesn't "measure" if you pass in nullptr as the buffer.
@@ -3566,71 +3486,6 @@
     return static_cast<jlong>(reinterpret_cast<uintptr_t>(bio.release()));
 }
 
-static int NativeCrypto_BIO_read(JNIEnv* env, jclass, jlong bioRef, jbyteArray outputJavaBytes) {
-    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
-    JNI_TRACE("BIO_read(%p, %p)", bio, outputJavaBytes);
-
-    if (outputJavaBytes == nullptr) {
-        Errors::jniThrowNullPointerException(env, "output == null");
-        JNI_TRACE("BIO_read(%p, %p) => output == null", bio, outputJavaBytes);
-        return 0;
-    }
-
-    jsize outputSize = env->GetArrayLength(outputJavaBytes);
-
-    std::unique_ptr<unsigned char[]> buffer(
-            new unsigned char[static_cast<unsigned int>(outputSize)]);
-    if (buffer.get() == nullptr) {
-        Errors::jniThrowOutOfMemory(env, "Unable to allocate buffer for read");
-        return 0;
-    }
-
-    int read = BIO_read(bio, buffer.get(), static_cast<int>(outputSize));
-    if (read <= 0) {
-        Errors::throwIOException(env, "BIO_read");
-        JNI_TRACE("BIO_read(%p, %p) => threw IO exception", bio, outputJavaBytes);
-        return 0;
-    }
-
-    env->SetByteArrayRegion(outputJavaBytes, 0, read, reinterpret_cast<jbyte*>(buffer.get()));
-    JNI_TRACE("BIO_read(%p, %p) => %d", bio, outputJavaBytes, read);
-    return read;
-}
-
-static void NativeCrypto_BIO_write(JNIEnv* env, jclass, jlong bioRef, jbyteArray inputJavaBytes,
-        jint offset, jint length) {
-    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
-    JNI_TRACE("BIO_write(%p, %p, %d, %d)", bio, inputJavaBytes, offset, length);
-
-    if (inputJavaBytes == nullptr) {
-        Errors::jniThrowNullPointerException(env, "input == null");
-        return;
-    }
-
-    int inputSize = env->GetArrayLength(inputJavaBytes);
-    if (offset < 0 || offset > inputSize || length < 0 || length > inputSize - offset) {
-        Errors::jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", "inputJavaBytes");
-        JNI_TRACE("BIO_write(%p, %p, %d, %d) => IOOB", bio, inputJavaBytes, offset, length);
-        return;
-    }
-
-    std::unique_ptr<unsigned char[]> buffer(new unsigned char[static_cast<unsigned int>(length)]);
-    if (buffer.get() == nullptr) {
-        Errors::jniThrowOutOfMemory(env, "Unable to allocate buffer for write");
-        return;
-    }
-
-    env->GetByteArrayRegion(inputJavaBytes, offset, length, reinterpret_cast<jbyte*>(buffer.get()));
-    if (BIO_write(bio, buffer.get(), length) != length) {
-        ERR_clear_error();
-        Errors::throwIOException(env, "BIO_write");
-        JNI_TRACE("BIO_write(%p, %p, %d, %d) => IO error", bio, inputJavaBytes, offset, length);
-        return;
-    }
-
-    JNI_TRACE("BIO_write(%p, %p, %d, %d) => success", bio, inputJavaBytes, offset, length);
-}
-
 static void NativeCrypto_BIO_free_all(JNIEnv* env, jclass, jlong bioRef) {
     BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
     JNI_TRACE("BIO_free_all(%p)", bio);
@@ -4456,48 +4311,6 @@
                         sec);
 }
 
-static jstring NativeCrypto_OBJ_txt2nid_oid(JNIEnv* env, jclass, jstring oidStr) {
-    JNI_TRACE("OBJ_txt2nid_oid(%p)", oidStr);
-
-    ScopedUtfChars oid(env, oidStr);
-    if (oid.c_str() == nullptr) {
-        return nullptr;
-    }
-
-    JNI_TRACE("OBJ_txt2nid_oid(%s)", oid.c_str());
-
-    int nid = OBJ_txt2nid(oid.c_str());
-    if (nid == NID_undef) {
-        JNI_TRACE("OBJ_txt2nid_oid(%s) => NID_undef", oid.c_str());
-        ERR_clear_error();
-        return nullptr;
-    }
-
-    const ASN1_OBJECT* obj = OBJ_nid2obj(nid);
-    if (obj == nullptr) {
-        Errors::throwExceptionIfNecessary(env, "OBJ_nid2obj");
-        return nullptr;
-    }
-
-    ScopedLocalRef<jstring> ouputStr(env, ASN1_OBJECT_to_OID_string(env, obj));
-    JNI_TRACE("OBJ_txt2nid_oid(%s) => %p", oid.c_str(), ouputStr.get());
-    return ouputStr.release();
-}
-
-static jstring NativeCrypto_X509_NAME_print_ex(JNIEnv* env, jclass, jlong x509NameRef, jlong jflags) {
-    X509_NAME* x509name = reinterpret_cast<X509_NAME*>(static_cast<uintptr_t>(x509NameRef));
-    unsigned long flags = static_cast<unsigned long>(jflags);
-    JNI_TRACE("X509_NAME_print_ex(%p, %ld)", x509name, flags);
-
-    if (x509name == nullptr) {
-        Errors::jniThrowNullPointerException(env, "x509name == null");
-        JNI_TRACE("X509_NAME_print_ex(%p, %ld) => x509name == null", x509name, flags);
-        return nullptr;
-    }
-
-    return X509_NAME_to_jstring(env, x509name, flags);
-}
-
 template <typename T, T* (*d2i_func)(BIO*, T**)>
 static jlong d2i_ASN1Object_to_jlong(JNIEnv* env, jlong bioRef) {
     BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
@@ -5846,6 +5659,79 @@
     return static_cast<unsigned int>(keyLen);
 }
 
+static int new_session_callback(SSL* ssl, SSL_SESSION* session) {
+    JNI_TRACE("ssl=%p new_session_callback session=%p", ssl, session);
+
+    AppData* appData = toAppData(ssl);
+    JNIEnv* env = appData->env;
+    if (env == nullptr) {
+        ALOGE("AppData->env missing in new_session_callback");
+        JNI_TRACE("ssl=%p new_session_callback env error", ssl);
+        return 0;
+    }
+    if (env->ExceptionCheck()) {
+        JNI_TRACE("ssl=%p new_session_callback already pending exception", ssl);
+        return 0;
+    }
+
+    jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks;
+    jclass cls = env->GetObjectClass(sslHandshakeCallbacks);
+    jmethodID methodID = env->GetMethodID(cls, "onNewSessionEstablished", "(J)V");
+    JNI_TRACE("ssl=%p new_session_callback calling onNewSessionEstablished", ssl);
+    env->CallVoidMethod(sslHandshakeCallbacks, methodID, reinterpret_cast<jlong>(session));
+    if (env->ExceptionCheck()) {
+        JNI_TRACE("ssl=%p new_session_callback exception cleared", ssl);
+        env->ExceptionClear();
+    }
+    JNI_TRACE("ssl=%p new_session_callback completed", ssl);
+
+    // Always returning 0 (not taking ownership). The Java code is responsible for incrementing
+    // the reference count.
+    return 0;
+}
+
+static SSL_SESSION* server_session_requested_callback(SSL* ssl, uint8_t* id, int id_len,
+                                                      int* out_copy) {
+    JNI_TRACE("ssl=%p server_session_requested_callback", ssl);
+
+    // Always set to out_copy to zero. The Java callback will be responsible for incrementing
+    // the reference count (and any required synchronization).
+    *out_copy = 0;
+
+    AppData* appData = toAppData(ssl);
+    JNIEnv* env = appData->env;
+    if (env == nullptr) {
+        ALOGE("AppData->env missing in server_session_requested_callback");
+        JNI_TRACE("ssl=%p server_session_requested_callback env error", ssl);
+        return 0;
+    }
+    if (env->ExceptionCheck()) {
+        JNI_TRACE("ssl=%p server_session_requested_callback already pending exception", ssl);
+        return 0;
+    }
+
+    // Copy the ID to a byte[].
+    jbyteArray id_array = env->NewByteArray(static_cast<jsize>(id_len));
+    if (id_array == nullptr) {
+        JNI_TRACE("ssl=%p id_array bytes == null => 0", ssl);
+        return 0;
+    }
+    env->SetByteArrayRegion(id_array, 0, static_cast<jsize>(id_len),
+                            reinterpret_cast<const jbyte*>(id));
+
+    jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks;
+    jclass cls = env->GetObjectClass(sslHandshakeCallbacks);
+    jmethodID methodID = env->GetMethodID(cls, "serverSessionRequested", "([B)J");
+    JNI_TRACE("ssl=%p server_session_requested_callback calling serverSessionRequested", ssl);
+    jlong ssl_session_address = env->CallLongMethod(sslHandshakeCallbacks, methodID, id_array);
+    if (env->ExceptionCheck()) {
+        JNI_TRACE("ssl=%p server_session_requested_callback exception cleared", ssl);
+        env->ExceptionClear();
+    }
+    JNI_TRACE("ssl=%p server_session_requested_callback completed => %d", ssl, ssl_session_address);
+    return reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address));
+}
+
 static jint NativeCrypto_EVP_has_aes_hardware(JNIEnv*, jclass) {
     int ret = 0;
     ret = EVP_has_aes_hardware();
@@ -5959,6 +5845,14 @@
         SSL_CTX_set_keylog_callback(sslCtx.get(), debug_print_session_key);
     }
 
+    // By default BoringSSL will cache in server mode, but we want to get
+    // notified of new sessions being created in client mode. We set
+    // SSL_SESS_CACHE_BOTH in order to get the callback in client mode, but
+    // ignore it in server mode in favor of the internal cache.
+    SSL_CTX_set_session_cache_mode(sslCtx.get(), SSL_SESS_CACHE_BOTH);
+    SSL_CTX_sess_set_new_cb(sslCtx.get(), new_session_callback);
+    SSL_CTX_sess_set_get_cb(sslCtx.get(), server_session_requested_callback);
+
     // Disable RSA-PSS deliberately until CryptoUpcalls supports it.
     if (!SSL_CTX_set_signing_algorithm_prefs(
                 sslCtx.get(), kDefaultSignatureAlgorithms,
@@ -6016,6 +5910,18 @@
     JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_CTX_set_session_id_context => ok", ssl_ctx);
 }
 
+static jlong NativeCrypto_SSL_CTX_set_timeout(JNIEnv* env, jclass, jlong ssl_ctx_address,
+                                             jlong seconds)
+{
+    SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true);
+    JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_CTX_set_timeout seconds=%d", ssl_ctx, (int) seconds);
+    if (ssl_ctx == nullptr) {
+        return 0L;
+    }
+
+    return SSL_CTX_set_timeout(ssl_ctx, static_cast<uint32_t>(seconds));
+}
+
 /**
  * public static native int SSL_new(long ssl_ctx) throws SSLException;
  */
@@ -6315,20 +6221,6 @@
 }
 
 /**
- * public static native long SSL_get_mode(long ssl);
- */
-static jlong NativeCrypto_SSL_get_mode(JNIEnv* env, jclass, jlong ssl_address) {
-    SSL* ssl = to_SSL(env, ssl_address, true);
-    JNI_TRACE("ssl=%p NativeCrypto_SSL_get_mode", ssl);
-    if (ssl == nullptr) {
-        return 0;
-    }
-    long mode = static_cast<long>(SSL_get_mode(ssl));
-    JNI_TRACE("ssl=%p NativeCrypto_SSL_get_mode => 0x%lx", ssl, mode);
-    return mode;
-}
-
-/**
  * public static native long SSL_set_mode(long ssl, long mode);
  */
 static jlong NativeCrypto_SSL_set_mode(JNIEnv* env, jclass,
@@ -6344,36 +6236,6 @@
 }
 
 /**
- * public static native long SSL_clear_mode(long ssl, long mode);
- */
-static jlong NativeCrypto_SSL_clear_mode(JNIEnv* env, jclass,
-        jlong ssl_address, jlong mode) {
-    SSL* ssl = to_SSL(env, ssl_address, true);
-    JNI_TRACE("ssl=%p NativeCrypto_SSL_clear_mode mode=0x%llx", ssl, (long long) mode);
-    if (ssl == nullptr) {
-        return 0;
-    }
-    long result = static_cast<long>(SSL_clear_mode(ssl, static_cast<uint32_t>(mode)));
-    JNI_TRACE("ssl=%p NativeCrypto_SSL_clear_mode => 0x%lx", ssl, result);
-    return result;
-}
-
-/**
- * public static native long SSL_get_options(long ssl);
- */
-static jlong NativeCrypto_SSL_get_options(JNIEnv* env, jclass,
-        jlong ssl_address) {
-    SSL* ssl = to_SSL(env, ssl_address, true);
-    JNI_TRACE("ssl=%p NativeCrypto_SSL_get_options", ssl);
-    if (ssl == nullptr) {
-        return 0;
-    }
-    long options = static_cast<long>(SSL_get_options(ssl));
-    JNI_TRACE("ssl=%p NativeCrypto_SSL_get_options => 0x%lx", ssl, options);
-    return options;
-}
-
-/**
  * public static native long SSL_set_options(long ssl, long options);
  */
 static jlong NativeCrypto_SSL_set_options(JNIEnv* env, jclass,
@@ -7108,33 +6970,27 @@
     JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => success", ssl);
 }
 
-/**
- * Perform SSL renegotiation
- */
-static void NativeCrypto_SSL_renegotiate(JNIEnv* env, jclass, jlong ssl_address)
-{
+static jstring NativeCrypto_SSL_get_current_cipher(JNIEnv* env, jclass, jlong ssl_address) {
     SSL* ssl = to_SSL(env, ssl_address, true);
-    JNI_TRACE("ssl=%p NativeCrypto_SSL_renegotiate", ssl);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_get_current_cipher", ssl);
     if (ssl == nullptr) {
-        return;
+        return nullptr;
     }
-    int result = SSL_renegotiate(ssl);
-    if (result != 1) {
-        Errors::throwSSLExceptionStr(env, "Problem with SSL_renegotiate");
-        return;
+    const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl);
+    const char* name = SSL_CIPHER_get_name(cipher);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_get_current_cipher => %s", ssl, name);
+    return env->NewStringUTF(name);
+}
+
+static jstring NativeCrypto_SSL_get_version(JNIEnv* env, jclass, jlong ssl_address) {
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_get_version", ssl);
+    if (ssl == nullptr) {
+        return nullptr;
     }
-    // first call asks client to perform renegotiation
-    int ret = SSL_do_handshake(ssl);
-    if (ret != 1) {
-        OpenSslError sslError(ssl, ret);
-        Errors::throwSSLExceptionWithSslErrors(env, ssl, sslError.release(),
-                                       "Problem with SSL_do_handshake after SSL_renegotiate");
-        return;
-    }
-    // if client agrees, set ssl state and perform renegotiation
-    SSL_set_state(ssl, SSL_ST_ACCEPT);
-    SSL_do_handshake(ssl);
-    JNI_TRACE("ssl=%p NativeCrypto_SSL_renegotiate =>", ssl);
+    const char* protocol = SSL_get_version(ssl);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_get_version => %s", ssl, protocol);
+    return env->NewStringUTF(protocol);
 }
 
 /**
@@ -7954,6 +7810,114 @@
 }
 
 /**
+ * Gets and returns in a long integer the creation's time of the
+ * actual SSL session.
+ */
+static jlong NativeCrypto_SSL_get_time(JNIEnv* env, jclass, jlong ssl_address) {
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_get_time", ssl);
+    if (ssl == nullptr) {
+        return 0;
+    }
+
+    SSL_SESSION* ssl_session = SSL_get_session(ssl);
+    JNI_TRACE("ssl_session=%p NativeCrypto_SSL_get_time", ssl_session);
+    if (ssl_session == nullptr) {
+        // BoringSSL does not protect against a NULL session.
+        return 0;
+    }
+    // result must be jlong, not long or *1000 will overflow
+    jlong result = SSL_SESSION_get_time(ssl_session);
+    result *= 1000; // OpenSSL uses seconds, Java uses milliseconds.
+    JNI_TRACE("ssl_session=%p NativeCrypto_SSL_get_time => %lld", ssl_session, (long long) result);
+    return result;
+}
+
+/**
+ * Sets the timeout on the SSL session.
+ */
+static jlong NativeCrypto_SSL_set_timeout(JNIEnv* env, jclass, jlong ssl_address, jlong millis) {
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_set_timeout", ssl);
+    if (ssl == nullptr) {
+        return 0;
+    }
+
+    SSL_SESSION* ssl_session = SSL_get_session(ssl);
+    JNI_TRACE("ssl_session=%p NativeCrypto_SSL_set_timeout", ssl_session);
+    if (ssl_session == nullptr) {
+        // BoringSSL does not protect against a NULL session.
+        return 0;
+    }
+
+    // Convert to seconds
+    long timeout = millis / 1000;
+    return SSL_set_timeout(ssl_session, timeout);
+}
+
+/**
+ * Gets the timeout for the SSL session.
+ */
+static jlong NativeCrypto_SSL_get_timeout(JNIEnv* env, jclass, jlong ssl_address) {
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_get_timeout", ssl);
+    if (ssl == nullptr) {
+        return 0;
+    }
+
+    SSL_SESSION* ssl_session = SSL_get_session(ssl);
+    JNI_TRACE("ssl_session=%p NativeCrypto_SSL_get_timeout", ssl_session);
+    if (ssl_session == nullptr) {
+        // BoringSSL does not protect against a NULL session.
+        return 0;
+    }
+
+    jlong result = SSL_get_timeout(ssl_session);
+    result *= 1000; // OpenSSL uses seconds, Java uses milliseconds.
+    JNI_TRACE("ssl_session=%p NativeCrypto_SSL_get_timeout => %lld", ssl_session, (long long) result);
+    return result;
+}
+
+/**
+ * Gets the timeout for the SSL session.
+ */
+static jlong NativeCrypto_SSL_SESSION_get_timeout(JNIEnv* env, jclass, jlong ssl_session_address) {
+    SSL_SESSION* ssl_session = to_SSL_SESSION(env, ssl_session_address, true);
+    JNI_TRACE("ssl_session=%p NativeCrypto_SSL_SESSION_get_timeout", ssl_session);
+    if (ssl_session == nullptr) {
+        return 0;
+    }
+
+    return SSL_get_timeout(ssl_session);
+}
+
+/**
+ * Gets the ID for the SSL session, or null if no session is currently available.
+ */
+static jbyteArray NativeCrypto_SSL_session_id(JNIEnv* env, jclass, jlong ssl_address) {
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_session_id", ssl);
+    if (ssl == nullptr) {
+        return nullptr;
+    }
+
+    SSL_SESSION* ssl_session = SSL_get_session(ssl);
+    JNI_TRACE("ssl_session=%p NativeCrypto_SSL_session_id", ssl_session);
+    if (ssl_session == nullptr) {
+        return nullptr;
+    }
+
+    jbyteArray result = env->NewByteArray(static_cast<jsize>(ssl_session->session_id_length));
+    if (result != nullptr) {
+        jbyte* src = reinterpret_cast<jbyte*>(ssl_session->session_id);
+        env->SetByteArrayRegion(result, 0, static_cast<jsize>(ssl_session->session_id_length), src);
+    }
+    JNI_TRACE("ssl_session=%p NativeCrypto_SSL_session_id => %p session_id_length=%d",
+             ssl_session, result, ssl_session->session_id_length);
+    return result;
+}
+
+/**
  * Gets and returns in a string the version of the SSL protocol. If it
  * returns the string "unknown" it means that no connection is established.
  */
@@ -7983,17 +7947,16 @@
     return env->NewStringUTF(name);
 }
 
-static jstring NativeCrypto_get_SSL_SESSION_tlsext_hostname(JNIEnv* env, jclass, jlong sessionJava) {
-    SSL_SESSION* ssl_session = to_SSL_SESSION(env, sessionJava, true);
-    JNI_TRACE("ssl_session=%p NativeCrypto_get_SSL_SESSION_tlsext_hostname", ssl_session);
-    if (ssl_session == nullptr || ssl_session->tlsext_hostname == nullptr) {
-        JNI_TRACE("ssl_session=%p NativeCrypto_get_SSL_SESSION_tlsext_hostname => null",
-                  ssl_session);
-        return nullptr;
+/**
+ * Increments the reference count of the session.
+ */
+static void NativeCrypto_SSL_SESSION_up_ref(JNIEnv* env, jclass, jlong ssl_session_address) {
+    SSL_SESSION* ssl_session = to_SSL_SESSION(env, ssl_session_address, true);
+    JNI_TRACE("ssl_session=%p NativeCrypto_SSL_SESSION_up_ref", ssl_session);
+    if (ssl_session == nullptr) {
+        return;
     }
-    JNI_TRACE("ssl_session=%p NativeCrypto_get_SSL_SESSION_tlsext_hostname => \"%s\"",
-              ssl_session, ssl_session->tlsext_hostname);
-    return env->NewStringUTF(ssl_session->tlsext_hostname);
+    SSL_SESSION_up_ref(ssl_session);
 }
 
 /**
@@ -8389,22 +8352,6 @@
     return static_cast<jint>(BIO_ctrl_pending(bio));
 }
 
-static jlong NativeCrypto_SSL_get0_session(JNIEnv* env, jclass, jlong ssl_address) {
-    SSL* ssl = to_SSL(env, ssl_address, true);
-    if (ssl == nullptr) {
-        return 0;
-    }
-    return reinterpret_cast<uintptr_t>(SSL_get0_session(ssl));
-}
-
-static jlong NativeCrypto_SSL_get1_session(JNIEnv* env, jclass, jlong ssl_address) {
-    SSL* ssl = to_SSL(env, ssl_address, true);
-    if (ssl == nullptr) {
-        return 0;
-    }
-    return reinterpret_cast<uintptr_t>(SSL_get1_session(ssl));
-}
-
 static jint NativeCrypto_SSL_max_seal_overhead(JNIEnv* env, jclass, jlong ssl_address) {
     SSL* ssl = to_SSL(env, ssl_address, true);
     if (ssl == nullptr) {
@@ -9071,7 +9018,7 @@
         Errors::throwSSLExceptionStr(env, "Unable to set appdata callback");
         ERR_clear_error();
         safeSslClear(ssl);
-        JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake_netty => exception", ssl);
+        JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_write_heap => exception", ssl);
         return -1;
     }
 
@@ -9087,6 +9034,156 @@
     return result;
 }
 
+// TESTING METHODS BEGIN
+
+static int NativeCrypto_BIO_read(JNIEnv* env, jclass, jlong bioRef, jbyteArray outputJavaBytes) {
+    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    JNI_TRACE("BIO_read(%p, %p)", bio, outputJavaBytes);
+
+    if (outputJavaBytes == nullptr) {
+        Errors::jniThrowNullPointerException(env, "output == null");
+        JNI_TRACE("BIO_read(%p, %p) => output == null", bio, outputJavaBytes);
+        return 0;
+    }
+
+    jsize outputSize = env->GetArrayLength(outputJavaBytes);
+
+    std::unique_ptr<unsigned char[]> buffer(
+            new unsigned char[static_cast<unsigned int>(outputSize)]);
+    if (buffer.get() == nullptr) {
+        Errors::jniThrowOutOfMemory(env, "Unable to allocate buffer for read");
+        return 0;
+    }
+
+    int read = BIO_read(bio, buffer.get(), static_cast<int>(outputSize));
+    if (read <= 0) {
+        Errors::throwIOException(env, "BIO_read");
+        JNI_TRACE("BIO_read(%p, %p) => threw IO exception", bio, outputJavaBytes);
+        return 0;
+    }
+
+    env->SetByteArrayRegion(outputJavaBytes, 0, read, reinterpret_cast<jbyte*>(buffer.get()));
+    JNI_TRACE("BIO_read(%p, %p) => %d", bio, outputJavaBytes, read);
+    return read;
+}
+
+static void NativeCrypto_BIO_write(JNIEnv* env, jclass, jlong bioRef, jbyteArray inputJavaBytes,
+        jint offset, jint length) {
+    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    JNI_TRACE("BIO_write(%p, %p, %d, %d)", bio, inputJavaBytes, offset, length);
+
+    if (inputJavaBytes == nullptr) {
+        Errors::jniThrowNullPointerException(env, "input == null");
+        return;
+    }
+
+    int inputSize = env->GetArrayLength(inputJavaBytes);
+    if (offset < 0 || offset > inputSize || length < 0 || length > inputSize - offset) {
+        Errors::jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", "inputJavaBytes");
+        JNI_TRACE("BIO_write(%p, %p, %d, %d) => IOOB", bio, inputJavaBytes, offset, length);
+        return;
+    }
+
+    std::unique_ptr<unsigned char[]> buffer(new unsigned char[static_cast<unsigned int>(length)]);
+    if (buffer.get() == nullptr) {
+        Errors::jniThrowOutOfMemory(env, "Unable to allocate buffer for write");
+        return;
+    }
+
+    env->GetByteArrayRegion(inputJavaBytes, offset, length, reinterpret_cast<jbyte*>(buffer.get()));
+    if (BIO_write(bio, buffer.get(), length) != length) {
+        ERR_clear_error();
+        Errors::throwIOException(env, "BIO_write");
+        JNI_TRACE("BIO_write(%p, %p, %d, %d) => IO error", bio, inputJavaBytes, offset, length);
+        return;
+    }
+
+    JNI_TRACE("BIO_write(%p, %p, %d, %d) => success", bio, inputJavaBytes, offset, length);
+}
+
+/**
+ * public static native long SSL_clear_mode(long ssl, long mode);
+ */
+static jlong NativeCrypto_SSL_clear_mode(JNIEnv* env, jclass,
+        jlong ssl_address, jlong mode) {
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_clear_mode mode=0x%llx", ssl, (long long) mode);
+    if (ssl == nullptr) {
+        return 0;
+    }
+    long result = static_cast<long>(SSL_clear_mode(ssl, static_cast<uint32_t>(mode)));
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_clear_mode => 0x%lx", ssl, result);
+    return result;
+}
+
+/**
+ * public static native long SSL_get_mode(long ssl);
+ */
+static jlong NativeCrypto_SSL_get_mode(JNIEnv* env, jclass, jlong ssl_address) {
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_get_mode", ssl);
+    if (ssl == nullptr) {
+        return 0;
+    }
+    long mode = static_cast<long>(SSL_get_mode(ssl));
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_get_mode => 0x%lx", ssl, mode);
+    return mode;
+}
+
+/**
+ * public static native long SSL_get_options(long ssl);
+ */
+static jlong NativeCrypto_SSL_get_options(JNIEnv* env, jclass,
+        jlong ssl_address) {
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_get_options", ssl);
+    if (ssl == nullptr) {
+        return 0;
+    }
+    long options = static_cast<long>(SSL_get_options(ssl));
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_get_options => 0x%lx", ssl, options);
+    return options;
+}
+
+static jlong NativeCrypto_SSL_get1_session(JNIEnv* env, jclass, jlong ssl_address) {
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    if (ssl == nullptr) {
+        return 0;
+    }
+    return reinterpret_cast<uintptr_t>(SSL_get1_session(ssl));
+}
+
+/**
+ * Perform SSL renegotiation
+ */
+static void NativeCrypto_SSL_renegotiate(JNIEnv* env, jclass, jlong ssl_address)
+{
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_renegotiate", ssl);
+    if (ssl == nullptr) {
+        return;
+    }
+    int result = SSL_renegotiate(ssl);
+    if (result != 1) {
+        Errors::throwSSLExceptionStr(env, "Problem with SSL_renegotiate");
+        return;
+    }
+    // first call asks client to perform renegotiation
+    int ret = SSL_do_handshake(ssl);
+    if (ret != 1) {
+        OpenSslError sslError(ssl, ret);
+        Errors::throwSSLExceptionWithSslErrors(env, ssl, sslError.release(),
+                                       "Problem with SSL_do_handshake after SSL_renegotiate");
+        return;
+    }
+    // if client agrees, set ssl state and perform renegotiation
+    SSL_set_state(ssl, SSL_ST_ACCEPT);
+    SSL_do_handshake(ssl);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_renegotiate => OK", ssl);
+}
+
+// TESTING METHODS END
+
 #define CONSCRYPT_NATIVE_METHOD(className, functionName, signature) \
     {                                                               \
         (char*)#functionName, (char*)(signature),                   \
@@ -9110,7 +9207,6 @@
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_PKEY_new_RSA, "([B[B[B[B[B[B[B[B)J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_PKEY_new_EC_KEY, "(" REF_EC_GROUP REF_EC_POINT "[B)J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_PKEY_type, "(" REF_EVP_PKEY ")I"),
-        CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_PKEY_size, "(" REF_EVP_PKEY ")I"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_PKEY_print_public, "(" REF_EVP_PKEY ")Ljava/lang/String;"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_PKEY_print_params, "(" REF_EVP_PKEY ")Ljava/lang/String;"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_PKEY_free, "(J)V"),
@@ -9165,7 +9261,6 @@
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_DigestUpdateDirect, "(" REF_EVP_MD_CTX "JI)V"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_DigestFinal_ex, "(" REF_EVP_MD_CTX "[BI)I"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_get_digestbyname, "(Ljava/lang/String;)J"),
-        CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_MD_block_size, "(J)I"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_MD_size, "(J)I"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_DigestSignInit, "(" REF_EVP_MD_CTX "J" REF_EVP_PKEY ")J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_DigestSignUpdate, "(" REF_EVP_MD_CTX "[BII)V"),
@@ -9201,7 +9296,6 @@
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_aead_aes_256_gcm, "()J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_AEAD_max_overhead, "(J)I"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_AEAD_nonce_length, "(J)I"),
-        CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_AEAD_max_tag_len, "(J)I"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_AEAD_CTX_seal, "(J[BI[BI[B[BII[B)I"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, EVP_AEAD_CTX_open, "(J[BI[BI[B[BII[B)I"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, HMAC_CTX_new, "()J"),
@@ -9211,15 +9305,9 @@
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, HMAC_UpdateDirect, "(" REF_HMAC_CTX "JI)V"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, HMAC_Final, "(" REF_HMAC_CTX ")[B"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, RAND_bytes, "([B)V"),
-        CONSCRYPT_NATIVE_METHOD(NativeCrypto, OBJ_txt2nid, "(Ljava/lang/String;)I"),
-        CONSCRYPT_NATIVE_METHOD(NativeCrypto, OBJ_txt2nid_longName, "(Ljava/lang/String;)Ljava/lang/String;"),
-        CONSCRYPT_NATIVE_METHOD(NativeCrypto, OBJ_txt2nid_oid, "(Ljava/lang/String;)Ljava/lang/String;"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, create_BIO_InputStream, ("(" REF_BIO_IN_STREAM "Z)J")),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, create_BIO_OutputStream, "(Ljava/io/OutputStream;)J"),
-        CONSCRYPT_NATIVE_METHOD(NativeCrypto, BIO_read, "(J[B)I"),
-        CONSCRYPT_NATIVE_METHOD(NativeCrypto, BIO_write, "(J[BII)V"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, BIO_free_all, "(J)V"),
-        CONSCRYPT_NATIVE_METHOD(NativeCrypto, X509_NAME_print_ex, "(JJ)Ljava/lang/String;"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, d2i_X509_bio, "(J)J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, d2i_X509, "([B)J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, i2d_X509, "(J)[B"),
@@ -9292,6 +9380,7 @@
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_CTX_new, "()J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_CTX_free, "(J)V"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_CTX_set_session_id_context, "(J[B)V"),
+        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_CTX_set_timeout, "(JJ)J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_new, "(J)J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_enable_tls_channel_id, "(J)V"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_get_tls_channel_id, "(J)[B"),
@@ -9300,10 +9389,7 @@
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_use_certificate, "(J[J)V"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_check_private_key, "(J)V"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_set_client_CA_list, "(J[[B)V"),
-        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_get_mode, "(J)J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_set_mode, "(JJ)J"),
-        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_clear_mode, "(JJ)J"),
-        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_get_options, "(J)J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_set_options, "(JJ)J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_clear_options, "(JJ)J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_enable_signed_cert_timestamps, "(J)V"),
@@ -9327,7 +9413,8 @@
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_set_tlsext_host_name, "(JLjava/lang/String;)V"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_get_servername, "(J)Ljava/lang/String;"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_do_handshake, "(J" FILE_DESCRIPTOR SSL_CALLBACKS "I)V"),
-        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_renegotiate, "(J)V"),
+        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_get_current_cipher, "(J)Ljava/lang/String;"),
+        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_get_version, "(J)Ljava/lang/String;"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_get_certificate, "(J)[J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_get_peer_cert_chain, "(J)[J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_read, "(J" FILE_DESCRIPTOR SSL_CALLBACKS "[BIII)I"),
@@ -9339,9 +9426,14 @@
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_free, "(J)V"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_SESSION_session_id, "(J)[B"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_SESSION_get_time, "(J)J"),
+        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_get_time, "(J)J"),
+        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_set_timeout, "(JJ)J"),
+        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_get_timeout, "(J)J"),
+        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_SESSION_get_timeout, "(J)J"),
+        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_session_id, "(J)[B"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_SESSION_get_version, "(J)Ljava/lang/String;"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_SESSION_cipher, "(J)Ljava/lang/String;"),
-        CONSCRYPT_NATIVE_METHOD(NativeCrypto, get_SSL_SESSION_tlsext_hostname, "(J)Ljava/lang/String;"),
+        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_SESSION_up_ref, "(J)V"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_SESSION_free, "(J)V"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, i2d_SSL_SESSION, "(J)[B"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, d2i_SSL_SESSION, "([B)J"),
@@ -9352,8 +9444,6 @@
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, get_ocsp_single_extension, "([BLjava/lang/String;JJ)[B"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, getDirectBufferAddress, "(Ljava/nio/Buffer;)J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_BIO_new, "(J)J"),
-        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_get0_session, "(J)J"),
-        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_get1_session, "(J)J"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_max_seal_overhead, "(J)I"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_clear_error, "()V"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_pending_readable_bytes, "(J)I"),
@@ -9372,6 +9462,15 @@
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, ENGINE_SSL_write_BIO_heap, "(JJ[BII" SSL_CALLBACKS ")I"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, ENGINE_SSL_read_BIO_heap, "(JJ[BII" SSL_CALLBACKS ")I"),
         CONSCRYPT_NATIVE_METHOD(NativeCrypto, ENGINE_SSL_shutdown, "(J" SSL_CALLBACKS ")V"),
+
+        // Used for testing only.
+        CONSCRYPT_NATIVE_METHOD(NativeCrypto, BIO_read, "(J[B)I"),
+        CONSCRYPT_NATIVE_METHOD(NativeCrypto, BIO_write, "(J[BII)V"),
+        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_clear_mode, "(JJ)J"),
+        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_get_mode, "(J)J"),
+        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_get_options, "(J)J"),
+        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_get1_session, "(J)J"),
+        CONSCRYPT_NATIVE_METHOD(NativeCrypto, SSL_renegotiate, "(J)V"),
 };
 
 void NativeCrypto::registerNativeMethods(JNIEnv* env) {
diff --git a/common/src/main/java/org/conscrypt/AbstractOpenSSLSession.java b/common/src/main/java/org/conscrypt/AbstractOpenSSLSession.java
deleted file mode 100644
index e5de29a..0000000
--- a/common/src/main/java/org/conscrypt/AbstractOpenSSLSession.java
+++ /dev/null
@@ -1,362 +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 java.security.Principal;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSessionBindingEvent;
-import javax.net.ssl.SSLSessionBindingListener;
-import javax.net.ssl.SSLSessionContext;
-import javax.security.cert.CertificateException;
-
-/**
- * Extends the base SSLSession with some methods used exclusively in Conscrypt.
- */
-abstract class AbstractOpenSSLSession implements SSLSession {
-    private final Map<String, Object> values = new HashMap<String, Object>();
-
-    private volatile javax.security.cert.X509Certificate[] peerCertificateChain;
-
-    private AbstractSessionContext sessionContext;
-
-    private boolean isValid = true;
-
-    /**
-     * Class constructor creates an SSL session context given the appropriate
-     * session context.
-     */
-    AbstractOpenSSLSession(AbstractSessionContext sessionContext) {
-        this.sessionContext = sessionContext;
-    }
-
-    protected abstract X509Certificate[] getX509PeerCertificates()
-            throws SSLPeerUnverifiedException;
-
-    protected abstract X509Certificate[] getX509LocalCertificates();
-
-    /**
-     * Throw SSLPeerUnverifiedException on null or empty peerCertificates array
-     */
-    private void checkPeerCertificatesPresent() throws SSLPeerUnverifiedException {
-        X509Certificate[] peerCertificates = getX509PeerCertificates();
-        if (peerCertificates == null || peerCertificates.length == 0) {
-            throw new SSLPeerUnverifiedException("No peer certificates");
-        }
-    }
-
-    /**
-     * Return the identity of the peer in this SSL session
-     * determined via certificate(s).
-     * @return an array of X509 certificates (the peer's one first and then
-     *         eventually that of the certification authority) or null if no
-     *         certificate were used during the SSL connection.
-     * @throws SSLPeerUnverifiedException if either a non-X.509 certificate
-     *         was used (i.e. Kerberos certificates) or the peer could not
-     *         be verified.
-     */
-    @Override
-    public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
-        return getX509PeerCertificates();
-    }
-
-    /**
-     * Returns the certificate(s) of the peer in this SSL session
-     * used in the handshaking phase of the connection.
-     * Please notice hat this method is superseded by
-     * <code>getPeerCertificates()</code>.
-     * @return an array of X509 certificates (the peer's one first and then
-     *         eventually that of the certification authority) or null if no
-     *         certificate were used during the SSL connection.
-     * @throws SSLPeerUnverifiedException if either a non-X.509 certificate
-     *         was used (i.e. Kerberos certificates) or the peer could not
-     *         be verified.
-     */
-    @Override
-    public javax.security.cert.X509Certificate[] getPeerCertificateChain()
-            throws SSLPeerUnverifiedException {
-        checkPeerCertificatesPresent();
-        javax.security.cert.X509Certificate[] result = peerCertificateChain;
-        if (result == null) {
-            // single-check idiom
-            peerCertificateChain = result = createPeerCertificateChain();
-        }
-        return result;
-    }
-
-    /**
-     * Provide a value to initialize the volatile peerCertificateChain
-     * field based on the native SSL_SESSION
-     */
-    private javax.security.cert.X509Certificate[] createPeerCertificateChain()
-            throws SSLPeerUnverifiedException {
-        X509Certificate[] peerCertificates = getX509PeerCertificates();
-        try {
-            javax.security.cert.X509Certificate[] chain =
-                    new javax.security.cert.X509Certificate[peerCertificates.length];
-
-            for (int i = 0; i < peerCertificates.length; i++) {
-                byte[] encoded = peerCertificates[i].getEncoded();
-                chain[i] = javax.security.cert.X509Certificate.getInstance(encoded);
-            }
-            return chain;
-        } catch (CertificateEncodingException e) {
-            SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
-            exception.initCause(exception);
-            throw exception;
-        } catch (CertificateException e) {
-            SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
-            exception.initCause(exception);
-            throw exception;
-        }
-    }
-
-    /**
-     * The identity of the principal that was used by the peer during the SSL
-     * handshake phase is returned by this method.
-     * @return a X500Principal of the last certificate for X509-based
-     *         cipher suites.
-     * @throws SSLPeerUnverifiedException if either a non-X.509 certificate
-     *         was used (i.e. Kerberos certificates) or the peer does not exist.
-     *
-     */
-    @Override
-    public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
-        checkPeerCertificatesPresent();
-        return getX509PeerCertificates()[0].getSubjectX500Principal();
-    }
-
-    /**
-     * Returns the principal (subject) of this concrete SSL session used in the
-     * handshaking phase of the connection.
-     * @return a X509 certificate or null if no principal was defined
-     */
-    @Override
-    public Principal getLocalPrincipal() {
-        X509Certificate[] localCertificates = getX509LocalCertificates();
-        if (localCertificates != null && localCertificates.length > 0) {
-            return localCertificates[0].getSubjectX500Principal();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Returns the certificate(s) of the principal (subject) of this concrete SSL
-     * session used in the handshaking phase of the connection. The OpenSSL
-     * native method supports only RSA certificates.
-     * @return an array of certificates (the local one first and then eventually
-     *         that of the certification authority) or null if no certificate
-     *         were used during the handshaking phase.
-     */
-    @Override
-    public Certificate[] getLocalCertificates() {
-        return getX509LocalCertificates();
-    }
-
-    /**
-     * Returns the largest buffer size for the application's data bound to this
-     * concrete SSL session.
-     * @return the largest buffer size
-     */
-    @Override
-    public int getApplicationBufferSize() {
-        return NativeConstants.SSL3_RT_MAX_PLAIN_LENGTH;
-    }
-
-    /**
-     * Returns the largest SSL/TLS packet size one can expect for this concrete
-     * SSL session.
-     * @return the largest packet size
-     */
-    @Override
-    public int getPacketBufferSize() {
-        return NativeConstants.SSL3_RT_MAX_PACKET_SIZE;
-    }
-
-    /**
-     * Returns the object which is bound to the the input parameter name.
-     * This name is a sort of link to the data of the SSL session's application
-     * layer, if any exists.
-     *
-     * @param name the name of the binding to find.
-     * @return the value bound to that name, or null if the binding does not
-     *         exist.
-     * @throws IllegalArgumentException if the argument is null.
-     */
-    @Override
-    public Object getValue(String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("name == null");
-        }
-        return values.get(name);
-    }
-
-    /**
-     * Returns an array with the names (sort of links) of all the data
-     * objects of the application layer bound into the SSL session.
-     *
-     * @return a non-null (possibly empty) array of names of the data objects
-     *         bound to this SSL session.
-     */
-    @Override
-    public String[] getValueNames() {
-        return values.keySet().toArray(new String[values.size()]);
-    }
-
-    /**
-     * A link (name) with the specified value object of the SSL session's
-     * application layer data is created or replaced. If the new (or existing)
-     * value object implements the <code>SSLSessionBindingListener</code>
-     * interface, that object will be notified in due course.
-     *
-     * @param name the name of the link (no null are
-     *            accepted!)
-     * @param value data object that shall be bound to
-     *            name.
-     * @throws IllegalArgumentException if one or both argument(s) is null.
-     */
-    @Override
-    public void putValue(String name, Object value) {
-        if (name == null || value == null) {
-            throw new IllegalArgumentException("name == null || value == null");
-        }
-        Object old = values.put(name, value);
-        if (value instanceof SSLSessionBindingListener) {
-            ((SSLSessionBindingListener) value).valueBound(new SSLSessionBindingEvent(this, name));
-        }
-        if (old instanceof SSLSessionBindingListener) {
-            ((SSLSessionBindingListener) old).valueUnbound(new SSLSessionBindingEvent(this, name));
-        }
-    }
-
-    /**
-     * Removes a link (name) with the specified value object of the SSL
-     * session's application layer data.
-     *
-     * <p>If the value object implements the <code>SSLSessionBindingListener</code>
-     * interface, the object will receive a <code>valueUnbound</code> notification.
-     *
-     * @param name the name of the link (no null are
-     *            accepted!)
-     * @throws IllegalArgumentException if the argument is null.
-     */
-    @Override
-    public void removeValue(String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("name == null");
-        }
-        Object old = values.remove(name);
-        if (old instanceof SSLSessionBindingListener) {
-            SSLSessionBindingListener listener = (SSLSessionBindingListener) old;
-            listener.valueUnbound(new SSLSessionBindingEvent(this, name));
-        }
-    }
-
-    /**
-     * Returns the context to which the actual SSL session is bound. A SSL
-     * context consists of (1) a possible delegate, (2) a provider and (3) a
-     * protocol.
-     * @return the SSL context used for this session, or null if it is
-     * unavailable.
-     */
-    @Override
-    public SSLSessionContext getSessionContext() {
-        return sessionContext;
-    }
-
-    /**
-     * Returns a boolean flag signaling whether a SSL session is valid
-     * and available for resuming or joining or not.
-     *
-     * @return true if this session may be resumed.
-     */
-    @Override
-    public boolean isValid() {
-        if (!isValid) {
-            return false;
-        }
-        // The session has't yet been invalidated -- check whether it timed out.
-
-        SSLSessionContext context = getSessionContext();
-        if (context == null) {
-            // Session not associated with a context -- no way to tell what its timeout should be.
-            return true;
-        }
-
-        int timeoutSeconds = context.getSessionTimeout();
-        if (timeoutSeconds == 0) {
-            // Infinite timeout -- session still valid
-            return true;
-        }
-
-        long creationTimestampMillis = getCreationTime();
-        long ageSeconds = (System.currentTimeMillis() - creationTimestampMillis) / 1000;
-        // NOTE: The age might be negative if something was/is wrong with the system clock. We time
-        // out such sessions to be safe.
-        if ((ageSeconds >= timeoutSeconds) || (ageSeconds < 0)) {
-            // Session timed out -- no longer valid
-            isValid = false;
-            return false;
-        }
-
-        // Session still valid
-        return true;
-    }
-
-    /**
-     * It invalidates a SSL session forbidding any resumption.
-     */
-    @Override
-    public void invalidate() {
-        isValid = false;
-        sessionContext = null;
-    }
-
-    /**
-     * Returns the name requested by the SNI extension.
-     */
-    public abstract String getRequestedServerName();
-
-    /**
-     * Returns the OCSP stapled response.
-     */
-    public abstract List<byte[]> getStatusResponses();
-
-    /**
-     * Returns the TLS Stapled Certificate Transparency data.
-     */
-    public abstract byte[] getTlsSctData();
-
-    /**
-     * Sets the last accessed time for this session in milliseconds since Jan 1,
-     * 1970 00:00:00 UTC.
-     */
-    public abstract void setLastAccessedTime(long accessTimeMillis);
-
-    /**
-     * Indicates that this session's ID may have changed and should be
-     * re-cached.
-     */
-    abstract void resetId();
-}
diff --git a/common/src/main/java/org/conscrypt/AbstractSessionContext.java b/common/src/main/java/org/conscrypt/AbstractSessionContext.java
index 61d595d..a7171f1 100644
--- a/common/src/main/java/org/conscrypt/AbstractSessionContext.java
+++ b/common/src/main/java/org/conscrypt/AbstractSessionContext.java
@@ -16,19 +16,10 @@
 
 package org.conscrypt;
 
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import javax.net.ssl.SSLSession;
@@ -50,28 +41,22 @@
 
     final long sslCtxNativePointer = NativeCrypto.SSL_CTX_new();
 
-    /** Identifies OpenSSL sessions. */
-    private static final int OPEN_SSL = 1;
-
-    /** Identifies OpenSSL sessions with OCSP stapled data. */
-    private static final int OPEN_SSL_WITH_OCSP = 2;
-
-    /** Identifies OpenSSL sessions with TLS SCT data. */
-    private static final int OPEN_SSL_WITH_TLS_SCT = 3;
-
     @SuppressWarnings("serial")
-    private final Map<ByteArray, SSLSession> sessions = new LinkedHashMap<ByteArray, SSLSession>() {
-        @Override
-        protected boolean removeEldestEntry(
-                Map.Entry<ByteArray, SSLSession> eldest) {
-            boolean remove = maximumSize > 0 && size() > maximumSize;
-            if (remove) {
-                remove(eldest.getKey());
-                sessionRemoved(eldest.getValue());
-            }
-            return false;
-        }
-    };
+    private final Map<ByteArray, SslSessionWrapper> sessions =
+            new LinkedHashMap<ByteArray, SslSessionWrapper>() {
+                @Override
+                protected boolean removeEldestEntry(
+                        Map.Entry<ByteArray, SslSessionWrapper> eldest) {
+                    // NOTE: does not take into account any session that may have become
+                    // invalid.
+                    if (maximumSize > 0 && size() > maximumSize) {
+                        // Let the subclass know.
+                        onBeforeRemoveSession(eldest.getValue());
+                        return true;
+                    }
+                    return false;
+                }
+            };
 
     /**
      * Constructs a new session context.
@@ -83,29 +68,27 @@
     }
 
     /**
-     * Returns the collection of sessions ordered from oldest to newest
+     * This method is provided for API-compatibility only, not intended for use. No guarantees
+     * are made WRT performance.
      */
-    private Iterator<SSLSession> sessionIterator() {
-        synchronized (sessions) {
-            SSLSession[] array = sessions.values().toArray(
-                    new SSLSession[sessions.size()]);
-            return Arrays.asList(array).iterator();
-        }
-    }
-
     @Override
     public final Enumeration<byte[]> getIds() {
-        final Iterator<SSLSession> i = sessionIterator();
+        // Make a copy of the IDs.
+        final Iterator<SslSessionWrapper> iter;
+        synchronized (sessions) {
+            iter = Arrays.asList(sessions.values().toArray(new SslSessionWrapper[sessions.size()]))
+                    .iterator();
+        }
         return new Enumeration<byte[]>() {
-            private SSLSession next;
+            private SslSessionWrapper next;
 
             @Override
             public boolean hasMoreElements() {
                 if (next != null) {
                     return true;
                 }
-                while (i.hasNext()) {
-                    SSLSession session = i.next();
+                while (iter.hasNext()) {
+                    SslSessionWrapper session = iter.next();
                     if (session.isValid()) {
                         next = session;
                         return true;
@@ -127,6 +110,26 @@
         };
     }
 
+    /**
+     * This is provided for API-compatibility only, not intended for use. No guarantees are
+     * made WRT performance or the validity of the returned session.
+     */
+    @Override
+    public final SSLSession getSession(byte[] sessionId) {
+        if (sessionId == null) {
+            throw new NullPointerException("sessionId");
+        }
+        ByteArray key = new ByteArray(sessionId);
+        SslSessionWrapper session;
+        synchronized (sessions) {
+            session = sessions.get(key);
+        }
+        if (session != null && session.isValid()) {
+            return session.toSSLSession();
+        }
+        return null;
+    }
+
     @Override
     public final int getSessionCacheSize() {
         return maximumSize;
@@ -137,55 +140,33 @@
         return timeout;
     }
 
-    /**
-     * Makes sure cache size is < maximumSize.
-     */
-    private void trimToSize() {
-        synchronized (sessions) {
-            int size = sessions.size();
-            if (size > maximumSize) {
-                int removals = size - maximumSize;
-                Iterator<SSLSession> i = sessions.values().iterator();
-                do {
-                    SSLSession session = i.next();
-                    i.remove();
-                    sessionRemoved(session);
-                } while (--removals > 0);
-            }
-        }
-    }
-
     @Override
-    public void setSessionTimeout(int seconds)
-            throws IllegalArgumentException {
+    public final void setSessionTimeout(int seconds) throws IllegalArgumentException {
         if (seconds < 0) {
             throw new IllegalArgumentException("seconds < 0");
         }
-        timeout = seconds;
 
         synchronized (sessions) {
-            Iterator<SSLSession> i = sessions.values().iterator();
+            // Set the timeout on this context.
+            timeout = seconds;
+            NativeCrypto.SSL_CTX_set_timeout(sslCtxNativePointer, seconds);
+
+            Iterator<SslSessionWrapper> i = sessions.values().iterator();
             while (i.hasNext()) {
-                SSLSession session = i.next();
+                SslSessionWrapper session = i.next();
                 // SSLSession's know their context and consult the
                 // timeout as part of their validity condition.
                 if (!session.isValid()) {
+                    // Let the subclass know.
+                    onBeforeRemoveSession(session);
                     i.remove();
-                    sessionRemoved(session);
                 }
             }
         }
     }
 
-    /**
-     * Called when a session is removed. Used by ClientSessionContext
-     * to update its host-and-port based cache.
-     */
-    protected abstract void sessionRemoved(SSLSession session);
-
     @Override
-    public final void setSessionCacheSize(int size)
-            throws IllegalArgumentException {
+    public final void setSessionCacheSize(int size) throws IllegalArgumentException {
         if (size < 0) {
             throw new IllegalArgumentException("size < 0");
         }
@@ -199,202 +180,6 @@
         }
     }
 
-    /**
-     * Converts the given session to bytes.
-     *
-     * @return session data as bytes or null if the session can't be converted
-     */
-    byte[] toBytes(SSLSession session) {
-        // TODO: Support SSLSessionImpl, too.
-        if (!(session instanceof OpenSSLSessionImpl)) {
-            return null;
-        }
-
-        OpenSSLSessionImpl sslSession = (OpenSSLSessionImpl) session;
-        try {
-            ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            DataOutputStream daos = new DataOutputStream(baos);
-
-            daos.writeInt(OPEN_SSL_WITH_TLS_SCT); // session type ID
-
-            // Session data.
-            byte[] data = sslSession.getEncoded();
-            daos.writeInt(data.length);
-            daos.write(data);
-
-            // Certificates.
-            Certificate[] certs = session.getPeerCertificates();
-            daos.writeInt(certs.length);
-
-            for (Certificate cert : certs) {
-                data = cert.getEncoded();
-                daos.writeInt(data.length);
-                daos.write(data);
-            }
-
-            List<byte[]> ocspResponses = sslSession.getStatusResponses();
-            daos.writeInt(ocspResponses.size());
-            for (byte[] ocspResponse : ocspResponses) {
-                daos.writeInt(ocspResponse.length);
-                daos.write(ocspResponse);
-            }
-
-            byte[] tlsSctData = sslSession.getTlsSctData();
-            if (tlsSctData != null) {
-                daos.writeInt(tlsSctData.length);
-                daos.write(tlsSctData);
-            } else {
-                daos.writeInt(0);
-            }
-
-            // TODO: local certificates?
-
-            return baos.toByteArray();
-        } catch (IOException e) {
-            System.err.println("Failed to convert saved SSL Session: " + e.getMessage());
-            return null;
-        } catch (CertificateEncodingException e) {
-            log(e);
-            return null;
-        }
-    }
-
-    private static void checkRemaining(ByteBuffer buf, int length) throws IOException {
-        if (length < 0) {
-            throw new IOException("Length is negative: " + length);
-        }
-        if (length > buf.remaining()) {
-            throw new IOException(
-                    "Length of blob is longer than available: " + length + " > " + buf.remaining());
-        }
-    }
-
-    /**
-     * Creates a session from the given bytes.
-     *
-     * @return a session or null if the session can't be converted
-     */
-    OpenSSLSessionImpl toSession(byte[] data, String host, int port) {
-        ByteBuffer buf = ByteBuffer.wrap(data);
-        try {
-            int type = buf.getInt();
-            if (type != OPEN_SSL && type != OPEN_SSL_WITH_OCSP && type != OPEN_SSL_WITH_TLS_SCT) {
-                throw new IOException("Unexpected type ID: " + type);
-            }
-
-            int length = buf.getInt();
-            checkRemaining(buf, length);
-
-            byte[] sessionData = new byte[length];
-            buf.get(sessionData);
-
-            int count = buf.getInt();
-            checkRemaining(buf, count);
-
-            X509Certificate[] certs = new X509Certificate[count];
-            for (int i = 0; i < count; i++) {
-                length = buf.getInt();
-                checkRemaining(buf, length);
-
-                byte[] certData = new byte[length];
-                buf.get(certData);
-                try {
-                    certs[i] = OpenSSLX509Certificate.fromX509Der(certData);
-                } catch (Exception e) {
-                    throw new IOException("Can not read certificate " + i + "/" + count);
-                }
-            }
-
-            byte[] ocspData = null;
-            if (type >= OPEN_SSL_WITH_OCSP) {
-                // We only support one OCSP response now, but in the future
-                // we may support RFC 6961 which has multiple.
-                int countOcspResponses = buf.getInt();
-                checkRemaining(buf, countOcspResponses);
-
-                if (countOcspResponses >= 1) {
-                    int ocspLength = buf.getInt();
-                    checkRemaining(buf, ocspLength);
-
-                    ocspData = new byte[ocspLength];
-                    buf.get(ocspData);
-
-                    // Skip the rest of the responses.
-                    for (int i = 1; i < countOcspResponses; i++) {
-                        ocspLength = buf.getInt();
-                        checkRemaining(buf, ocspLength);
-                        buf.position(buf.position() + ocspLength);
-                    }
-                }
-            }
-
-            byte[] tlsSctData = null;
-            if (type == OPEN_SSL_WITH_TLS_SCT) {
-                int tlsSctDataLength = buf.getInt();
-                checkRemaining(buf, tlsSctDataLength);
-
-                if (tlsSctDataLength > 0) {
-                    tlsSctData = new byte[tlsSctDataLength];
-                    buf.get(tlsSctData);
-                }
-            }
-
-            if (buf.remaining() != 0) {
-                log(new AssertionError("Read entire session, but data still remains; rejecting"));
-                return null;
-            }
-
-            return new OpenSSLSessionImpl(sessionData, host, port, certs, ocspData, tlsSctData,
-                    this);
-        } catch (IOException e) {
-            log(e);
-            return null;
-        } catch (BufferUnderflowException e) {
-            log(e);
-            return null;
-        }
-    }
-
-    SSLSession wrapSSLSessionIfNeeded(SSLSession session) {
-        if (session instanceof AbstractOpenSSLSession) {
-            return Platform.wrapSSLSession((AbstractOpenSSLSession) session);
-        } else {
-            return session;
-        }
-    }
-
-    @Override
-    public SSLSession getSession(byte[] sessionId) {
-        if (sessionId == null) {
-            throw new NullPointerException("sessionId == null");
-        }
-        ByteArray key = new ByteArray(sessionId);
-        SSLSession session;
-        synchronized (sessions) {
-            session = sessions.get(key);
-        }
-        if (session != null && session.isValid()) {
-            return wrapSSLSessionIfNeeded(session);
-        }
-        return null;
-    }
-
-    void putSession(SSLSession session) {
-        byte[] id = session.getId();
-        if (id.length == 0) {
-            return;
-        }
-        ByteArray key = new ByteArray(id);
-        synchronized (sessions) {
-            sessions.put(key, session);
-        }
-    }
-
-    private static void log(Throwable t) {
-        System.out.println("Error inflating SSL session: "
-                + (t.getMessage() != null ? t.getMessage() : t.getClass().getName()));
-    }
-
     @Override
     protected void finalize() throws Throwable {
         try {
@@ -403,4 +188,85 @@
             super.finalize();
         }
     }
+
+    /**
+     * Adds the given session to the cache.
+     */
+    final void cacheSession(SslSessionWrapper session) {
+        byte[] id = session.getId();
+        if (id == null || id.length == 0) {
+            return;
+        }
+
+        // Let the subclass know.
+        onBeforeAddSession(session);
+
+        ByteArray key = new ByteArray(id);
+        synchronized (sessions) {
+            sessions.put(key, session);
+        }
+    }
+
+    /**
+     * Called for server sessions only. Retrieves the session by its ID. Overridden by
+     * {@link ServerSessionContext} to
+     */
+    final SslSessionWrapper getSessionFromCache(byte[] sessionId) {
+        if (sessionId == null) {
+            return null;
+        }
+
+        // First, look in the in-memory cache.
+        SslSessionWrapper session;
+        synchronized (sessions) {
+            session = sessions.get(new ByteArray(sessionId));
+        }
+        if (session != null && session.isValid()) {
+            return session;
+        }
+
+        // Not found in-memory - look it up in the persistent cache.
+        return getSessionFromPersistentCache(sessionId);
+    }
+
+    /**
+     * Called when the given session is about to be added. Used by {@link ClientSessionContext} to
+     * update its host-and-port based cache.
+     *
+     * <p>Visible for extension only, not intended to be called directly.
+     */
+    abstract void onBeforeAddSession(SslSessionWrapper session);
+
+    /**
+     * Called when a session is about to be removed. Used by {@link ClientSessionContext}
+     * to update its host-and-port based cache.
+     *
+     * <p>Visible for extension only, not intended to be called directly.
+     */
+    abstract void onBeforeRemoveSession(SslSessionWrapper session);
+
+    /**
+     * Called for server sessions only. Retrieves the session by ID from the persistent cache.
+     *
+     * <p>Visible for extension only, not intended to be called directly.
+     */
+    abstract SslSessionWrapper getSessionFromPersistentCache(byte[] sessionId);
+
+    /**
+     * Makes sure cache size is < maximumSize.
+     */
+    private void trimToSize() {
+        synchronized (sessions) {
+            int size = sessions.size();
+            if (size > maximumSize) {
+                int removals = size - maximumSize;
+                Iterator<SslSessionWrapper> i = sessions.values().iterator();
+                while (removals-- > 0) {
+                    SslSessionWrapper session = i.next();
+                    onBeforeRemoveSession(session);
+                    i.remove();
+                }
+            }
+        }
+    }
 }
diff --git a/common/src/main/java/org/conscrypt/ActiveSession.java b/common/src/main/java/org/conscrypt/ActiveSession.java
new file mode 100644
index 0000000..c7f6431
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/ActiveSession.java
@@ -0,0 +1,350 @@
+/*
+ * 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.checkNotNull;
+
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionBindingEvent;
+import javax.net.ssl.SSLSessionBindingListener;
+import javax.net.ssl.SSLSessionContext;
+
+/**
+ * A session that is dedicated a single connection and operates directly on the underlying
+ * {@code SSL}.
+ */
+final class ActiveSession implements SSLSession {
+    private final SslWrapper ssl;
+    private AbstractSessionContext sessionContext;
+    private byte[] id;
+    private long creationTime;
+    private String cipherSuite;
+    private String protocol;
+    private String peerHost;
+    private int peerPort = -1;
+    private long lastAccessedTime = 0;
+    private volatile javax.security.cert.X509Certificate[] peerCertificateChain;
+    private X509Certificate[] localCertificates;
+    private X509Certificate[] peerCertificates;
+    private byte[] peerCertificateOcspData;
+    private byte[] peerTlsSctData;
+
+    // lazy init for memory reasons
+    private Map<String, Object> values;
+
+    ActiveSession(SslWrapper ssl, AbstractSessionContext sessionContext) {
+        this.ssl = checkNotNull(ssl, "ssl");
+        this.sessionContext = checkNotNull(sessionContext, "sessionContext");
+    }
+
+    @Override
+    public byte[] getId() {
+        if (id == null) {
+            id = ssl.getSessionId();
+        }
+        return id != null ? id.clone() : EmptyArray.BYTE;
+    }
+
+    /**
+     * Indicates that this session's ID may have changed and should be re-cached.
+     */
+    void resetId() {
+        id = null;
+    }
+
+    @Override
+    public SSLSessionContext getSessionContext() {
+        return isValid() ? sessionContext : null;
+    }
+
+    @Override
+    public long getCreationTime() {
+        if (creationTime == 0) {
+            creationTime = ssl.getTime();
+        }
+        return creationTime;
+    }
+
+    /**
+     * Returns the last time this SSL session was accessed. Accessing
+     * here is to mean that a new connection with the same SSL context data was
+     * established.
+     *
+     * @return the session's last access time in milliseconds since the epoch
+     */
+    // TODO(nathanmittler): Does lastAccessedTime need to account for session reuse?
+    @Override
+    public long getLastAccessedTime() {
+        return lastAccessedTime == 0 ? getCreationTime() : lastAccessedTime;
+    }
+
+    void setLastAccessedTime(long accessTimeMillis) {
+        lastAccessedTime = accessTimeMillis;
+    }
+
+    /**
+     * Returns the OCSP stapled response. Returns a copy of the internal arrays.
+     *
+     * The method signature matches
+     * <a
+     * href="http://download.java.net/java/jdk9/docs/api/javax/net/ssl/ExtendedSSLSession.html#getStatusResponses--">Java
+     * 9</a>.
+     *
+     * @see <a href="https://tools.ietf.org/html/rfc6066">RFC 6066</a>
+     * @see <a href="https://tools.ietf.org/html/rfc6961">RFC 6961</a>
+     */
+    /* @Override */
+    @SuppressWarnings("MissingOverride") // For Pre-Java9 compatibility.
+    public List<byte[]> getStatusResponses() {
+        if (peerCertificateOcspData == null) {
+            return Collections.<byte[]>emptyList();
+        }
+
+        return Collections.singletonList(peerCertificateOcspData.clone());
+    }
+
+    /**
+     * Returns the signed certificate timestamp (SCT) received from the peer. Returns a
+     * copy of the internal array.
+     *
+     * @see <a href="https://tools.ietf.org/html/rfc6962">RFC 6962</a>
+     */
+    byte[] getPeerSignedCertificateTimestamp() {
+        if (peerTlsSctData == null) {
+            return null;
+        }
+        return peerTlsSctData.clone();
+    }
+
+    String getRequestedServerName() {
+        return ssl.getRequestedServerName();
+    }
+
+    @Override
+    public void invalidate() {
+        ssl.setTimeout(0L);
+    }
+
+    @Override
+    public boolean isValid() {
+        long creationTimeMillis = ssl.getTime();
+        long timeoutMillis = ssl.getTimeout();
+        return (System.currentTimeMillis() - timeoutMillis) < creationTimeMillis;
+    }
+
+    @Override
+    public void putValue(String name, Object value) {
+        if (name == null) {
+            throw new NullPointerException("name");
+        }
+        if (value == null) {
+            throw new NullPointerException("value");
+        }
+        Map<String, Object> values = this.values;
+        if (values == null) {
+            // Use size of 2 to keep the memory overhead small
+            values = this.values = new HashMap<String, Object>(2);
+        }
+        Object old = values.put(name, value);
+        if (value instanceof SSLSessionBindingListener) {
+            ((SSLSessionBindingListener) value).valueBound(new SSLSessionBindingEvent(this, name));
+        }
+        if (old instanceof SSLSessionBindingListener) {
+            ((SSLSessionBindingListener) old).valueUnbound(new SSLSessionBindingEvent(this, name));
+        }
+        notifyUnbound(old, name);
+    }
+
+    @Override
+    public Object getValue(String name) {
+        if (name == null) {
+            throw new NullPointerException("name");
+        }
+        if (values == null) {
+            return null;
+        }
+        return values.get(name);
+    }
+
+    @Override
+    public void removeValue(String name) {
+        if (name == null) {
+            throw new NullPointerException("name");
+        }
+        Map<String, Object> values = this.values;
+        if (values == null) {
+            return;
+        }
+        Object old = values.remove(name);
+        notifyUnbound(old, name);
+    }
+
+    @Override
+    public String[] getValueNames() {
+        Map<String, Object> values = this.values;
+        if (values == null || values.isEmpty()) {
+            return EmptyArray.STRING;
+        }
+        return values.keySet().toArray(new String[values.size()]);
+    }
+
+    @Override
+    public X509Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
+        checkPeerCertificatesPresent();
+        return peerCertificates.clone();
+    }
+
+    @Override
+    public Certificate[] getLocalCertificates() {
+        return localCertificates == null ? null : localCertificates.clone();
+    }
+
+    /**
+     * Returns the certificate(s) of the peer in this SSL session
+     * used in the handshaking phase of the connection.
+     * Please notice hat this method is superseded by
+     * <code>getPeerCertificates()</code>.
+     * @return an array of X509 certificates (the peer's one first and then
+     *         eventually that of the certification authority) or null if no
+     *         certificate were used during the SSL connection.
+     * @throws SSLPeerUnverifiedException if either a non-X.509 certificate
+     *         was used (i.e. Kerberos certificates) or the peer could not
+     *         be verified.
+     */
+    @Override
+    public javax.security.cert.X509Certificate[] getPeerCertificateChain()
+            throws SSLPeerUnverifiedException {
+        checkPeerCertificatesPresent();
+        // TODO(nathanmittler): Should we clone?
+        javax.security.cert.X509Certificate[] result = peerCertificateChain;
+        if (result == null) {
+            // single-check idiom
+            peerCertificateChain = result = SSLUtils.toCertificateChain(peerCertificates);
+        }
+        return result;
+    }
+
+    @Override
+    public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+        checkPeerCertificatesPresent();
+        return peerCertificates[0].getSubjectX500Principal();
+    }
+
+    @Override
+    public Principal getLocalPrincipal() {
+        if (localCertificates != null && localCertificates.length > 0) {
+            return localCertificates[0].getSubjectX500Principal();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public String getCipherSuite() {
+        if (cipherSuite == null) {
+            cipherSuite = ssl.getCipherSuite();
+        }
+        return cipherSuite;
+    }
+
+    @Override
+    public String getProtocol() {
+        String protocol = this.protocol;
+        if (protocol == null) {
+            protocol = ssl.getVersion();
+            this.protocol = protocol;
+        }
+        return protocol;
+    }
+
+    @Override
+    public String getPeerHost() {
+        return peerHost;
+    }
+
+    @Override
+    public int getPeerPort() {
+        return peerPort;
+    }
+
+    @Override
+    public int getPacketBufferSize() {
+        return NativeConstants.SSL3_RT_MAX_PACKET_SIZE;
+    }
+
+    @Override
+    public int getApplicationBufferSize() {
+        return NativeConstants.SSL3_RT_MAX_PLAIN_LENGTH;
+    }
+
+    /**
+     * Configures the peer information once it has been received by the handshake.
+     */
+    void onPeerCertificatesReceived(
+            String peerHost, int peerPort, OpenSSLX509Certificate[] peerCertificates) {
+        configurePeer(peerHost, peerPort, peerCertificates);
+    }
+
+    /**
+     * Configures the peer and local state from a newly created BoringSSL session.
+     */
+    void onSessionEstablished(String peerHost, int peerPort) {
+        id = null;
+        this.localCertificates = ssl.getLocalCertificates();
+        configurePeer(peerHost, peerPort, ssl.getPeerCertificates());
+    }
+
+    private void configurePeer(
+            String peerHost, int peerPort, OpenSSLX509Certificate[] peerCertificates) {
+        this.peerHost = peerHost;
+        this.peerPort = peerPort;
+        this.peerCertificates = peerCertificates;
+        this.peerCertificateOcspData = ssl.getPeerCertificateOcspData();
+        this.peerTlsSctData = ssl.getPeerTlsSctData();
+    }
+
+    private X509Certificate[] getX509PeerCertificates() throws SSLPeerUnverifiedException {
+        if (peerCertificates == null || peerCertificates.length == 0) {
+            throw new SSLPeerUnverifiedException("No peer certificates");
+        }
+        return peerCertificates;
+    }
+
+    /**
+     * Throw SSLPeerUnverifiedException on null or empty peerCertificates array
+     */
+    private void checkPeerCertificatesPresent() throws SSLPeerUnverifiedException {
+        if (peerCertificates == null || peerCertificates.length == 0) {
+            throw new SSLPeerUnverifiedException("No peer certificates");
+        }
+    }
+
+    private void notifyUnbound(Object value, String name) {
+        if (value instanceof SSLSessionBindingListener) {
+            ((SSLSessionBindingListener) value)
+                    .valueUnbound(new SSLSessionBindingEvent(this, name));
+        }
+    }
+}
diff --git a/common/src/main/java/org/conscrypt/ClientSessionContext.java b/common/src/main/java/org/conscrypt/ClientSessionContext.java
index 48bbe8e..6265a7b 100644
--- a/common/src/main/java/org/conscrypt/ClientSessionContext.java
+++ b/common/src/main/java/org/conscrypt/ClientSessionContext.java
@@ -17,7 +17,8 @@
 package org.conscrypt;
 
 import java.util.HashMap;
-import javax.net.ssl.SSLSession;
+import java.util.Map;
+import javax.net.ssl.SSLContext;
 
 /**
  * Caches client sessions. Indexes by host and port. Users are typically
@@ -26,39 +27,70 @@
  * @hide
  */
 @Internal
-public class ClientSessionContext extends AbstractSessionContext {
-
+public final class ClientSessionContext extends AbstractSessionContext {
     /**
      * Sessions indexed by host and port. Protect from concurrent
      * access by holding a lock on sessionsByHostAndPort.
      */
-    private final HashMap<HostAndPort, SSLSession> sessionsByHostAndPort = new HashMap<>();
+    @SuppressWarnings("serial")
+    private final Map<HostAndPort, SslSessionWrapper> sessionsByHostAndPort = new HashMap<>();
 
     private SSLClientSessionCache persistentCache;
 
-    public ClientSessionContext() {
+    ClientSessionContext() {
         super(10);
     }
 
-    public int size() {
-        return sessionsByHostAndPort.size();
-    }
-
+    /**
+     * Applications should not use this method. Instead use {@link
+     * Conscrypt.Contexts#setClientSessionCache(SSLContext, SSLClientSessionCache)}.
+     */
     public void setPersistentCache(SSLClientSessionCache persistentCache) {
         this.persistentCache = persistentCache;
     }
 
-    @Override
-    protected void sessionRemoved(SSLSession session) {
-        String host = session.getPeerHost();
-        int port = session.getPeerPort();
-        if (host == null) {
-            return;
+    /**
+     * Gets the suitable session reference from the session cache container.
+     */
+    SslSessionWrapper getCachedSession(String hostName, int port, SSLParametersImpl sslParameters) {
+        if (hostName == null) {
+            return null;
         }
-        HostAndPort hostAndPortKey = new HostAndPort(host, port);
-        synchronized (sessionsByHostAndPort) {
-            sessionsByHostAndPort.remove(hostAndPortKey);
+
+        SslSessionWrapper session = getSession(hostName, port);
+        if (session == null) {
+            return null;
         }
+
+        String protocol = session.getProtocol();
+        boolean protocolFound = false;
+        for (String enabledProtocol : sslParameters.enabledProtocols) {
+            if (protocol.equals(enabledProtocol)) {
+                protocolFound = true;
+                break;
+            }
+        }
+        if (!protocolFound) {
+            return null;
+        }
+
+        String cipherSuite = session.getCipherSuite();
+        boolean cipherSuiteFound = false;
+        for (String enabledCipherSuite : sslParameters.enabledCipherSuites) {
+            if (cipherSuite.equals(enabledCipherSuite)) {
+                cipherSuiteFound = true;
+                break;
+            }
+        }
+        if (!cipherSuiteFound) {
+            return null;
+        }
+
+        return session;
+    }
+
+    int size() {
+        return sessionsByHostAndPort.size();
     }
 
     /**
@@ -68,30 +100,30 @@
      * @param port of server
      * @return cached session or null if none found
      */
-    public SSLSession getSession(String host, int port) {
+    private SslSessionWrapper getSession(String host, int port) {
         if (host == null) {
             return null;
         }
-        SSLSession session;
-        HostAndPort hostAndPortKey = new HostAndPort(host, port);
+
+        HostAndPort key = new HostAndPort(host, port);
+        SslSessionWrapper session;
         synchronized (sessionsByHostAndPort) {
-            session = sessionsByHostAndPort.get(hostAndPortKey);
+            session = sessionsByHostAndPort.get(key);
         }
         if (session != null && session.isValid()) {
-            return wrapSSLSessionIfNeeded(session);
+            return session;
         }
 
         // Look in persistent cache.
         if (persistentCache != null) {
             byte[] data = persistentCache.getSessionData(host, port);
             if (data != null) {
-                session = toSession(data, host, port);
+                session = SslSessionWrapper.newInstance(this, data, host, port);
                 if (session != null && session.isValid()) {
-                    super.putSession(session);
                     synchronized (sessionsByHostAndPort) {
-                        sessionsByHostAndPort.put(hostAndPortKey, session);
+                        sessionsByHostAndPort.put(key, session);
                     }
-                    return wrapSSLSessionIfNeeded(session);
+                    return session;
                 }
             }
         }
@@ -100,30 +132,47 @@
     }
 
     @Override
-    public void putSession(SSLSession session) {
-        super.putSession(session);
-
+    void onBeforeAddSession(SslSessionWrapper session) {
         String host = session.getPeerHost();
         int port = session.getPeerPort();
         if (host == null) {
             return;
         }
 
-        HostAndPort hostAndPortKey = new HostAndPort(host, port);
+        HostAndPort key = new HostAndPort(host, port);
         synchronized (sessionsByHostAndPort) {
-            sessionsByHostAndPort.put(hostAndPortKey, session);
+            sessionsByHostAndPort.put(key, session);
         }
 
-        // TODO: This in a background thread.
+        // TODO: Do this in a background thread.
         if (persistentCache != null) {
-            byte[] data = toBytes(session);
+            byte[] data = session.toBytes();
             if (data != null) {
-                persistentCache.putSessionData(session, data);
+                persistentCache.putSessionData(session.toSSLSession(), data);
             }
         }
     }
 
-    static class HostAndPort {
+    @Override
+    void onBeforeRemoveSession(SslSessionWrapper session) {
+        String host = session.getPeerHost();
+        if (host == null) {
+            return;
+        }
+        int port = session.getPeerPort();
+        HostAndPort hostAndPortKey = new HostAndPort(host, port);
+        synchronized (sessionsByHostAndPort) {
+            sessionsByHostAndPort.remove(hostAndPortKey);
+        }
+    }
+
+    @Override
+    SslSessionWrapper getSessionFromPersistentCache(byte[] sessionId) {
+        // Not implemented for clients.
+        return null;
+    }
+
+    private static final class HostAndPort {
         final String host;
         final int port;
 
diff --git a/common/src/main/java/org/conscrypt/Conscrypt.java b/common/src/main/java/org/conscrypt/Conscrypt.java
index 10d23d1..3db8e9e 100644
--- a/common/src/main/java/org/conscrypt/Conscrypt.java
+++ b/common/src/main/java/org/conscrypt/Conscrypt.java
@@ -20,11 +20,13 @@
 import java.security.KeyManagementException;
 import java.security.PrivateKey;
 import java.security.Provider;
+import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLContextSpi;
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLEngineResult;
 import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSessionContext;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.X509TrustManager;
@@ -80,6 +82,44 @@
     }
 
     /**
+     * Utility methods for configuring Conscrypt {@link SSLContext} instances.
+     */
+    public static final class Contexts {
+        private Contexts() {}
+
+        /**
+         * Indicates whether the given object is a Conscrypt client-side session context.
+         */
+        public static boolean isConscrypt(SSLContext context) {
+            return context.getProvider() instanceof OpenSSLProvider;
+        }
+
+        /**
+         * Sets the client-side persistent cache to be used by the context.
+         */
+        public static void setClientSessionCache(SSLContext context, SSLClientSessionCache cache) {
+            SSLSessionContext clientContext = context.getClientSessionContext();
+            if (!(clientContext instanceof ClientSessionContext)) {
+                throw new IllegalArgumentException(
+                        "Not a conscrypt client context: " + clientContext.getClass().getName());
+            }
+            ((ClientSessionContext) clientContext).setPersistentCache(cache);
+        }
+
+        /**
+         * Sets the server-side persistent cache to be used by the context.
+         */
+        public static void setServerSessionCache(SSLContext context, SSLServerSessionCache cache) {
+            SSLSessionContext serverContext = context.getServerSessionContext();
+            if (!(serverContext instanceof ServerSessionContext)) {
+                throw new IllegalArgumentException(
+                        "Not a conscrypt client context: " + serverContext.getClass().getName());
+            }
+            ((ServerSessionContext) serverContext).setPersistentCache(cache);
+        }
+    }
+
+    /**
      * Utility methods for configuring Conscrypt socket factories.
      */
     public static final class SocketFactories {
diff --git a/common/src/main/java/org/conscrypt/ConscryptEngine.java b/common/src/main/java/org/conscrypt/ConscryptEngine.java
index d7b6d12..258d9bf 100644
--- a/common/src/main/java/org/conscrypt/ConscryptEngine.java
+++ b/common/src/main/java/org/conscrypt/ConscryptEngine.java
@@ -48,8 +48,6 @@
 import static org.conscrypt.NativeConstants.SSL_ERROR_WANT_READ;
 import static org.conscrypt.NativeConstants.SSL_ERROR_WANT_WRITE;
 import static org.conscrypt.NativeConstants.SSL_ERROR_ZERO_RETURN;
-import static org.conscrypt.NativeConstants.SSL_RECEIVED_SHUTDOWN;
-import static org.conscrypt.NativeConstants.SSL_SENT_SHUTDOWN;
 import static org.conscrypt.Preconditions.checkArgument;
 import static org.conscrypt.Preconditions.checkNotNull;
 import static org.conscrypt.SSLUtils.EngineStates.STATE_CLOSED;
@@ -88,6 +86,8 @@
 import javax.net.ssl.X509KeyManager;
 import javax.net.ssl.X509TrustManager;
 import javax.security.auth.x500.X500Principal;
+import org.conscrypt.NativeRef.SSL_SESSION;
+import org.conscrypt.SslWrapper.BioWrapper;
 
 /**
  * Implements the {@link SSLEngine} API using OpenSSL's non-blocking interfaces.
@@ -114,12 +114,12 @@
     private final SSLParametersImpl sslParameters;
 
     /**
-     * Protects {@link #engineState} and {@link #handshakeFinished}.
+     * Protects {@link #state} and {@link #handshakeFinished}.
      */
     private final Object stateLock = new Object();
 
     // @GuardedBy("stateLock");
-    private int engineState = STATE_NEW;
+    private int state = STATE_NEW;
     private boolean handshakeFinished;
 
     /**
@@ -127,24 +127,18 @@
      * close.
      */
     // @GuardedBy("stateLock");
-    private long sslNativePointer;
+    private final SslWrapper ssl;
 
     /**
-     * Protected by synchronizing on stateLock. Starts as 0, set by startHandshake, reset to 0 on
-     * close.
+     * The BIO used for reading/writing encrypted bytes.
      */
     // @GuardedBy("stateLock");
-    private long networkBio;
+    private final BioWrapper networkBio;
 
     /**
      * Set during startHandshake.
      */
-    private AbstractOpenSSLSession sslSession;
-
-    /**
-     * Used during handshake callbacks.
-     */
-    private AbstractOpenSSLSession handshakeSession;
+    private final ActiveSession sslSession;
 
     /**
      * Private key for the TLS Channel ID extension. This field is client-side only. Set during
@@ -165,16 +159,33 @@
     ConscryptEngine(SSLParametersImpl sslParameters) {
         this.sslParameters = sslParameters;
         peerInfoProvider = PeerInfoProvider.nullProvider();
+        this.ssl = newSsl(sslParameters, this);
+        this.networkBio = ssl.newBio();
+        sslSession = new ActiveSession(ssl, sslParameters.getSessionContext());
     }
 
     ConscryptEngine(final String host, final int port, SSLParametersImpl sslParameters) {
         this.sslParameters = sslParameters;
         this.peerInfoProvider = PeerInfoProvider.forHostAndPort(host, port);
+        this.ssl = newSsl(sslParameters, this);
+        this.networkBio = ssl.newBio();
+        sslSession = new ActiveSession(ssl, sslParameters.getSessionContext());
     }
 
     ConscryptEngine(SSLParametersImpl sslParameters, PeerInfoProvider peerInfoProvider) {
         this.sslParameters = sslParameters;
         this.peerInfoProvider = checkNotNull(peerInfoProvider, "peerInfoProvider");
+        this.ssl = newSsl(sslParameters, this);
+        this.networkBio = ssl.newBio();
+        sslSession = new ActiveSession(ssl, sslParameters.getSessionContext());
+    }
+
+    private static SslWrapper newSsl(SSLParametersImpl sslParameters, ConscryptEngine engine) {
+        try {
+            return SslWrapper.newInstance(sslParameters, engine, engine, engine);
+        } catch (SSLException e) {
+            throw new RuntimeException(e);
+        }
     }
 
     /**
@@ -225,7 +236,7 @@
                 throw new IllegalStateException(
                         "Channel ID is only available after handshake completes");
             }
-            return NativeCrypto.SSL_get_tls_channel_id(sslNativePointer);
+            return ssl.getTlsChannelId();
         }
     }
 
@@ -291,7 +302,7 @@
     }
 
     private boolean isHandshakeStarted() {
-        switch (engineState) {
+        switch (state) {
             case STATE_NEW:
             case STATE_MODE_SET:
                 return false;
@@ -336,7 +347,7 @@
     }
 
     private void beginHandshakeInternal() throws SSLException {
-        switch (engineState) {
+        switch (state) {
             case STATE_MODE_SET:
                 // This is the only allowed state.
                 break;
@@ -350,44 +361,24 @@
                 throw new IllegalStateException("Client/server mode must be set before handshake");
         }
 
-        engineState = STATE_HANDSHAKE_STARTED;
+        state = STATE_HANDSHAKE_STARTED;
 
         boolean releaseResources = true;
         try {
-            final AbstractSessionContext sessionContext = sslParameters.getSessionContext();
-            sslNativePointer = NativeCrypto.SSL_new(sessionContext.sslCtxNativePointer);
-            networkBio = NativeCrypto.SSL_BIO_new(sslNativePointer);
+            // Prepare the SSL object for the handshake.
+            ssl.initialize(getHostname(), channelIdPrivateKey);
 
-            // Allow servers to trigger renegotiation. Some inadvisable server
-            // configurations cause them to attempt to renegotiate during
-            // certain protocols.
-            NativeCrypto.SSL_accept_renegotiations(sslNativePointer);
-
+            // For clients, offer to resume a previously cached session to avoid the
+            // full TLS handshake.
             if (getUseClientMode()) {
-                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);
+                SslSessionWrapper cachedSession = clientSessionContext().getCachedSession(
+                        getHostname(), getPeerPort(), sslParameters);
+                if (cachedSession != null) {
+                    cachedSession.offerToResume(ssl);
                 }
             }
 
-            sslSession =
-                    sslParameters.getSessionToReuse(sslNativePointer, getPeerHost(), getPeerPort());
-
-            sslParameters.setSSLParameters(sslNativePointer, this, this, getHostname());
-            sslParameters.setCertificateValidation(sslNativePointer);
-            sslParameters.setTlsChannelId(sslNativePointer, channelIdPrivateKey);
-
-            maxSealOverhead = NativeCrypto.SSL_max_seal_overhead(sslNativePointer);
+            maxSealOverhead = ssl.getMaxSealOverhead();
             handshake();
             releaseResources = false;
         } catch (IOException e) {
@@ -401,7 +392,7 @@
             throw SSLUtils.toSSLHandshakeException(e);
         } finally {
             if (releaseResources) {
-                engineState = STATE_CLOSED;
+                state = STATE_CLOSED;
                 shutdownAndFreeSslNative();
             }
         }
@@ -410,13 +401,13 @@
     @Override
     public void closeInbound() throws SSLException {
         synchronized (stateLock) {
-            if (engineState == STATE_CLOSED) {
+            if (state == STATE_CLOSED) {
                 return;
             }
-            if (engineState == STATE_CLOSED_OUTBOUND) {
-                engineState = STATE_CLOSED;
+            if (state == STATE_CLOSED_OUTBOUND) {
+                state = STATE_CLOSED;
             } else {
-                engineState = STATE_CLOSED_INBOUND;
+                state = STATE_CLOSED_INBOUND;
             }
         }
         // TODO anything else to notify OpenSSL layer?
@@ -425,16 +416,16 @@
     @Override
     public void closeOutbound() {
         synchronized (stateLock) {
-            if (engineState == STATE_CLOSED || engineState == STATE_CLOSED_OUTBOUND) {
+            if (state == STATE_CLOSED || state == STATE_CLOSED_OUTBOUND) {
                 return;
             }
             if (isHandshakeStarted()) {
                 shutdownAndFreeSslNative();
             }
-            if (engineState == STATE_CLOSED_INBOUND) {
-                engineState = STATE_CLOSED;
+            if (state == STATE_CLOSED_INBOUND) {
+                state = STATE_CLOSED;
             } else {
-                engineState = STATE_CLOSED_OUTBOUND;
+                state = STATE_CLOSED_OUTBOUND;
             }
         }
         shutdown();
@@ -485,7 +476,7 @@
         if (handshakeFinished) {
             return HandshakeStatus.NOT_HANDSHAKING;
         }
-        switch (engineState) {
+        switch (state) {
             case STATE_HANDSHAKE_STARTED:
                 return pendingStatus(pendingOutboundEncryptedBytes());
             case STATE_HANDSHAKE_COMPLETED:
@@ -501,15 +492,15 @@
             default:
                 break;
         }
-        throw new IllegalStateException("Unexpected engine state: " + engineState);
+        throw new IllegalStateException("Unexpected engine state: " + state);
     }
 
     private int pendingOutboundEncryptedBytes() {
-        return NativeCrypto.SSL_pending_written_bytes_in_BIO(networkBio);
+        return networkBio.getPendingWrittenBytes();
     }
 
     private int pendingInboundCleartextBytes() {
-        return NativeCrypto.SSL_pending_readable_bytes(sslNativePointer);
+        return ssl.getPendingReadableBytes();
     }
 
     private static SSLEngineResult.HandshakeStatus pendingStatus(int pendingOutboundBytes) {
@@ -522,13 +513,30 @@
         return sslParameters.getNeedClientAuth();
     }
 
+    /* @Override */
+    @SuppressWarnings("MissingOverride") // For compilation with Java 6.
+    public SSLSession getHandshakeSession() {
+        return handshakeSession();
+    }
+
+    /**
+     * Work-around to allow this method to be called on older versions of Android.
+     */
+    SSLSession handshakeSession() {
+        synchronized (stateLock) {
+            return state >= STATE_HANDSHAKE_STARTED && state < STATE_READY ? sslSession : null;
+        }
+    }
+
     @Override
     public SSLSession getSession() {
-        if (sslSession == null) {
-            return handshakeSession != null ? Platform.wrapSSLSession(handshakeSession)
-                                            : SSLNullSession.getNullSession();
+        synchronized (stateLock) {
+            if (state < STATE_HANDSHAKE_COMPLETED) {
+                // Return an invalid session with invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
+                return SSLNullSession.getNullSession();
+            }
+            return Platform.wrapSSLSession(sslSession);
         }
-        return Platform.wrapSSLSession(sslSession);
     }
 
     @Override
@@ -553,22 +561,22 @@
 
     @Override
     public boolean isInboundDone() {
-        if (sslNativePointer == 0) {
+        if (ssl.isClosed()) {
             synchronized (stateLock) {
-                return engineState == STATE_CLOSED || engineState == STATE_CLOSED_INBOUND;
+                return state == STATE_CLOSED || state == STATE_CLOSED_INBOUND;
             }
         }
-        return (NativeCrypto.SSL_get_shutdown(sslNativePointer) & SSL_RECEIVED_SHUTDOWN) != 0;
+        return ssl.wasShutdownReceived();
     }
 
     @Override
     public boolean isOutboundDone() {
-        if (sslNativePointer == 0) {
+        if (ssl.isClosed()) {
             synchronized (stateLock) {
-                return engineState == STATE_CLOSED || engineState == STATE_CLOSED_OUTBOUND;
+                return state == STATE_CLOSED || state == STATE_CLOSED_OUTBOUND;
             }
         }
-        return (NativeCrypto.SSL_get_shutdown(sslNativePointer) & SSL_SENT_SHUTDOWN) != 0;
+        return ssl.wasShutdownSent();
     }
 
     @Override
@@ -596,9 +604,9 @@
         synchronized (stateLock) {
             if (isHandshakeStarted()) {
                 throw new IllegalArgumentException(
-                        "Can not change mode after handshake: engineState == " + engineState);
+                        "Can not change mode after handshake: state == " + state);
             }
-            engineState = STATE_MODE_SET;
+            state = STATE_MODE_SET;
         }
         sslParameters.setUseClientMode(mode);
     }
@@ -666,7 +674,7 @@
         final long srcLength = calcSrcsLength(srcs, srcsOffset, srcsEndOffset);
 
         synchronized (stateLock) {
-            switch (engineState) {
+            switch (state) {
                 case STATE_MODE_SET:
                     // Begin the handshake implicitly.
                     beginHandshakeInternal();
@@ -688,7 +696,7 @@
                 if (handshakeStatus == NEED_WRAP) {
                     return NEED_WRAP_OK;
                 }
-                if (engineState == STATE_CLOSED) {
+                if (state == STATE_CLOSED) {
                     return NEED_WRAP_CLOSED;
                 }
                 // NEED_UNWRAP - just fall through to perform the unwrap.
@@ -872,7 +880,6 @@
     }
 
     private SSLEngineResult.HandshakeStatus handshake() throws SSLException {
-        long sslSessionCtx = 0L;
         try {
             // Only actually perform the handshake if we haven't already just completed it
             // via BIO operations.
@@ -890,7 +897,7 @@
                     throw e;
                 }
 
-                int ssl_error_code = NativeCrypto.ENGINE_SSL_do_handshake(sslNativePointer, this);
+                int ssl_error_code = ssl.doHandshake();
                 switch (ssl_error_code) {
                     case SSL_ERROR_WANT_READ:
                         return pendingStatus(pendingOutboundEncryptedBytes());
@@ -917,28 +924,15 @@
                 throw e;
             }
 
-            // Handshake is finished!
-            sslSessionCtx = NativeCrypto.SSL_get1_session(sslNativePointer);
-            if (sslSessionCtx == 0) {
-                // TODO(nathanmittler): Should we throw here?
-                // return pendingStatus(pendingOutboundBytes());
-                throw shutdownWithError("Failed to obtain session after handshake completed");
-            }
-            sslSession = sslParameters.setupSession(sslSessionCtx, sslNativePointer, sslSession,
-                    getPeerHost(), getPeerPort(), true);
-            if (sslSession != null && engineState == STATE_HANDSHAKE_STARTED) {
-                engineState = STATE_READY_HANDSHAKE_CUT_THROUGH;
-            } else {
-                engineState = STATE_READY;
-            }
+            // The handshake has completed successfully...
+
+            // Update the session from the current state of the SSL object.
+            sslSession.onSessionEstablished(getPeerHost(), getPeerPort());
+
             finishHandshake();
             return FINISHED;
         } catch (Exception e) {
             throw toSSLHandshakeException(e);
-        } finally {
-            if (sslSession == null && sslSessionCtx != 0) {
-                NativeCrypto.SSL_SESSION_free(sslSessionCtx);
-            }
         }
     }
 
@@ -949,6 +943,7 @@
             handshakeListener.onHandshakeFinished();
         }
     }
+
     /**
      * Write plaintext data to the OpenSSL internal BIO
      *
@@ -961,11 +956,11 @@
 
             if (src.isDirect()) {
                 long addr = NativeCrypto.getDirectBufferAddress(src) + pos;
-                sslWrote = NativeCrypto.ENGINE_SSL_write_direct(sslNativePointer, addr, len, this);
+                sslWrote = ssl.writeDirectByteBuffer(addr, len);
             } else {
                 ByteBuffer heapSrc = toHeapBuffer(src, len);
-                sslWrote = NativeCrypto.ENGINE_SSL_write_heap(sslNativePointer, heapSrc.array(),
-                        heapSrc.arrayOffset() + heapSrc.position(), len, this);
+                sslWrote = ssl.writeArray(
+                        heapSrc.array(), heapSrc.arrayOffset() + heapSrc.position(), len);
             }
             if (sslWrote > 0) {
                 src.position(pos + sslWrote);
@@ -987,19 +982,18 @@
             final int len = Math.min(SSL3_RT_MAX_PACKET_SIZE, limit - pos);
             if (dst.isDirect()) {
                 long addr = NativeCrypto.getDirectBufferAddress(dst) + pos;
-                sslRead = NativeCrypto.ENGINE_SSL_read_direct(sslNativePointer, addr, len, this);
+                sslRead = ssl.readDirectByteBuffer(addr, len);
                 if (sslRead > 0) {
                     dst.position(pos + sslRead);
                 }
             } else if (dst.hasArray()) {
-                sslRead = NativeCrypto.ENGINE_SSL_read_heap(
-                        sslNativePointer, dst.array(), dst.arrayOffset() + pos, len, this);
+                sslRead = ssl.readArray(dst.array(), dst.arrayOffset() + pos, len);
                 if (sslRead > 0) {
                     dst.position(pos + sslRead);
                 }
             } else {
                 byte[] data = new byte[len];
-                sslRead = NativeCrypto.ENGINE_SSL_read_heap(sslNativePointer, data, 0, len, this);
+                sslRead = ssl.readArray(data, 0, len);
                 if (sslRead > 0) {
                     dst.put(data, 0, sslRead);
                 }
@@ -1026,12 +1020,11 @@
             final int netWrote;
             if (src.isDirect()) {
                 long addr = NativeCrypto.getDirectBufferAddress(src) + pos;
-                netWrote = NativeCrypto.ENGINE_SSL_write_BIO_direct(
-                        sslNativePointer, networkBio, addr, len, this);
+                netWrote = networkBio.writeDirectByteBuffer(addr, len);
             } else {
                 ByteBuffer heapSrc = toHeapBuffer(src, len);
-                netWrote = NativeCrypto.ENGINE_SSL_write_BIO_heap(sslNativePointer, networkBio,
-                        heapSrc.array(), heapSrc.arrayOffset() + heapSrc.position(), len, this);
+                netWrote = networkBio.writeArray(
+                        heapSrc.array(), heapSrc.arrayOffset() + heapSrc.position(), len);
             }
 
             if (netWrote >= 0) {
@@ -1095,23 +1088,20 @@
                 final int len = Math.min(pending, limit - pos);
                 if (dst.isDirect()) {
                     long addr = NativeCrypto.getDirectBufferAddress(dst) + pos;
-                    bioRead = NativeCrypto.ENGINE_SSL_read_BIO_direct(
-                            sslNativePointer, networkBio, addr, len, this);
+                    bioRead = networkBio.readDirectByteBuffer(addr, len);
                     if (bioRead > 0) {
                         dst.position(pos + bioRead);
                         return bioRead;
                     }
                 } else if (dst.hasArray()) {
-                    bioRead = NativeCrypto.ENGINE_SSL_read_BIO_heap(sslNativePointer, networkBio,
-                            dst.array(), dst.arrayOffset() + pos, pending, this);
+                    bioRead = networkBio.readArray(dst.array(), dst.arrayOffset() + pos, pending);
                     if (bioRead > 0) {
                         dst.position(pos + bioRead);
                         return bioRead;
                     }
                 } else {
                     byte[] data = new byte[len];
-                    bioRead = NativeCrypto.ENGINE_SSL_read_BIO_heap(
-                            sslNativePointer, networkBio, data, 0, pending, this);
+                    bioRead = networkBio.readArray(data, 0, pending);
                     if (bioRead > 0) {
                         dst.put(data, 0, bioRead);
                         return bioRead;
@@ -1140,7 +1130,7 @@
     }
 
     private SSLEngineResult.Status getEngineStatus() {
-        switch (engineState) {
+        switch (state) {
             case STATE_CLOSED_INBOUND:
             case STATE_CLOSED_OUTBOUND:
             case STATE_CLOSED:
@@ -1155,14 +1145,6 @@
         closeInbound();
     }
 
-    private SSLEngineResult sslReadErrorResult(int err, int bytesConsumed, int bytesProduced)
-            throws SSLException {
-        if (!handshakeFinished && pendingOutboundEncryptedBytes() > 0) {
-            return new SSLEngineResult(OK, NEED_WRAP, bytesConsumed, bytesProduced);
-        }
-        throw shutdownWithError(NativeCrypto.SSL_get_error_string(err));
-    }
-
     private SSLException shutdownWithError(String err) {
         // There was an internal error -- shutdown
         shutdown();
@@ -1201,7 +1183,7 @@
         }
 
         synchronized (stateLock) {
-            switch (engineState) {
+            switch (state) {
                 case STATE_MODE_SET:
                     // Begin the handshake implicitly.
                     beginHandshakeInternal();
@@ -1225,7 +1207,7 @@
                     return NEED_UNWRAP_OK;
                 }
 
-                if (engineState == STATE_CLOSED) {
+                if (state == STATE_CLOSED) {
                     return NEED_UNWRAP_CLOSED;
                 }
                 // NEED_WRAP - just fall through to perform the wrap.
@@ -1285,7 +1267,7 @@
                             break loop;
                         }
                     } else {
-                        int sslError = NativeCrypto.SSL_get_error(sslNativePointer, result);
+                        int sslError = ssl.getError(result);
                         switch (sslError) {
                             case SSL_ERROR_ZERO_RETURN:
                                 // This means the connection was shutdown correctly, close inbound
@@ -1358,12 +1340,12 @@
 
     @Override
     public int clientPSKKeyRequested(String identityHint, byte[] identity, byte[] key) {
-        return sslParameters.clientPSKKeyRequested(identityHint, identity, key, this);
+        return ssl.clientPSKKeyRequested(identityHint, identity, key);
     }
 
     @Override
     public int serverPSKKeyRequested(String identityHint, String identity, byte[] key) {
-        return sslParameters.serverPSKKeyRequested(identityHint, identity, key, this);
+        return ssl.serverPSKKeyRequested(identityHint, identity, key);
     }
 
     @Override
@@ -1373,16 +1355,16 @@
                 case SSL_CB_HANDSHAKE_START: {
                     // For clients, this will allow the NEED_UNWRAP status to be
                     // returned.
-                    engineState = STATE_HANDSHAKE_STARTED;
+                    state = STATE_HANDSHAKE_STARTED;
                     break;
                 }
                 case SSL_CB_HANDSHAKE_DONE: {
-                    if (engineState != STATE_HANDSHAKE_STARTED
-                            && engineState != STATE_READY_HANDSHAKE_CUT_THROUGH) {
+                    if (state != STATE_HANDSHAKE_STARTED
+                            && state != STATE_READY_HANDSHAKE_CUT_THROUGH) {
                         throw new IllegalStateException(
-                                "Completed handshake while in mode " + engineState);
+                                "Completed handshake while in mode " + state);
                     }
-                    engineState = STATE_HANDSHAKE_COMPLETED;
+                    state = STATE_HANDSHAKE_COMPLETED;
                     break;
                 }
             }
@@ -1390,6 +1372,33 @@
     }
 
     @Override
+    public void onNewSessionEstablished(long sslSessionNativePtr) {
+        try {
+            // Increment the reference count to "take ownership" of the session resource.
+            NativeCrypto.SSL_SESSION_up_ref(sslSessionNativePtr);
+
+            // Create a native reference which will release the SSL_SESSION in its finalizer.
+            // This constructor will only throw if the native pointer passed in is NULL, which
+            // BoringSSL guarantees will not happen.
+            NativeRef.SSL_SESSION ref = new SSL_SESSION(sslSessionNativePtr);
+
+            SslSessionWrapper sessionWrapper = SslSessionWrapper.newInstance(ref, sslSession);
+
+            // Cache the newly established session.
+            AbstractSessionContext ctx = sessionContext();
+            ctx.cacheSession(sessionWrapper);
+        } catch (Exception ignored) {
+            // Ignore.
+        }
+    }
+
+    @Override
+    public long serverSessionRequested(byte[] id) {
+        // TODO(nathanmittler): Implement server-side caching for TLS < 1.3
+        return 0;
+    }
+
+    @Override
     public void verifyCertificateChain(long[] certRefs, String authMethod)
             throws CertificateException {
         try {
@@ -1403,13 +1412,8 @@
             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, getPeerHost(), getPeerPort(), null);
+            // Update the peer information on the session.
+            sslSession.onPeerCertificatesReceived(getPeerHost(), getPeerPort(), peerCertChain);
 
             if (getUseClientMode()) {
                 Platform.checkServerTrusted(x509tm, peerCertChain, authMethod, this);
@@ -1421,21 +1425,18 @@
             throw e;
         } catch (Exception e) {
             throw new CertificateException(e);
-        } finally {
-            handshakeSession = null;
         }
     }
 
     @Override
     public void clientCertificateRequested(byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals)
             throws CertificateEncodingException, SSLException {
-        sslParameters.chooseClientCertificate(
-                keyTypeBytes, asn1DerEncodedPrincipals, sslNativePointer, this);
+        ssl.chooseClientCertificate(keyTypeBytes, asn1DerEncodedPrincipals);
     }
 
     private void shutdown() {
         try {
-            NativeCrypto.ENGINE_SSL_shutdown(sslNativePointer, this);
+            ssl.shutdown();
         } catch (IOException ignored) {
             // TODO: The RI ignores close failures in SSLSocket, but need to
             // investigate whether it does for SSLEngine.
@@ -1451,13 +1452,10 @@
     }
 
     private void free() {
-        if (sslNativePointer == 0) {
-            return;
+        if (!ssl.isClosed()) {
+            ssl.close();
+            networkBio.close();
         }
-        NativeCrypto.SSL_free(sslNativePointer);
-        NativeCrypto.BIO_free_all(networkBio);
-        sslNativePointer = 0;
-        networkBio = 0;
     }
 
     @Override
@@ -1469,22 +1467,6 @@
         }
     }
 
-    /* @Override */
-    @SuppressWarnings("MissingOverride") // For compilation with Java 6.
-    public SSLSession getHandshakeSession() {
-        return handshakeSession;
-    }
-
-    /**
-     * Work-around to allow this method to be called on older versions of Android.
-     */
-    SSLSession handshakeSession() {
-        if (handshakeSession != null) {
-            return Platform.wrapSSLSession(handshakeSession);
-        }
-        return null;
-    }
-
     @Override
     public String chooseServerAlias(X509KeyManager keyManager, String keyType) {
         if (keyManager instanceof X509ExtendedKeyManager) {
@@ -1556,7 +1538,7 @@
      * agreed upon.
      */
     byte[] getAlpnSelectedProtocol() {
-        return NativeCrypto.SSL_get0_alpn_selected(sslNativePointer);
+        return ssl.getAlpnSelectedProtocol();
     }
 
     private ByteBuffer toHeapBuffer(ByteBuffer buffer, int len) {
@@ -1597,6 +1579,14 @@
         singleDstBuffer[0] = null;
     }
 
+    private ClientSessionContext clientSessionContext() {
+        return sslParameters.getClientSessionContext();
+    }
+
+    private AbstractSessionContext sessionContext() {
+        return sslParameters.getSessionContext();
+    }
+
     private static void checkIndex(int arrayLength, int offset, int length, String arrayName) {
         if ((offset | length) < 0 || offset + length > arrayLength) {
             throw new IndexOutOfBoundsException("offset: " + offset + ", length: " + length
diff --git a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
index 433a6bc..f40efdd 100644
--- a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
+++ b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
@@ -34,19 +34,15 @@
 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 {
+final class ConscryptEngineSocket extends OpenSSLSocketImpl {
     private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
 
     private final ConscryptEngine engine;
@@ -225,7 +221,7 @@
     @Override
     public SSLSession getSession() {
         SSLSession session = engine.getSession();
-        if (session == null) {
+        if (SSLNullSession.isNullSession(session)) {
             boolean handshakeCompleted = false;
             try {
                 if (isConnected()) {
@@ -237,9 +233,8 @@
             }
 
             if (!handshakeCompleted) {
-                // return an invalid session with
-                // invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
-                return SSLNullSession.getNullSession();
+                // Return an invalid session with invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
+                return session;
             }
             session = engine.getSession();
         }
@@ -391,35 +386,6 @@
         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;
     }
diff --git a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
index 34f23c7..7db8654 100644
--- a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
+++ b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
@@ -17,7 +17,6 @@
 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;
@@ -44,6 +43,7 @@
 import javax.net.ssl.X509KeyManager;
 import javax.net.ssl.X509TrustManager;
 import javax.security.auth.x500.X500Principal;
+import org.conscrypt.NativeRef.SSL_SESSION;
 
 /**
  * Implementation of the class OpenSSLSocketImpl based on OpenSSL.
@@ -73,7 +73,7 @@
      * startHandshake, reset to 0 on close.
      */
     // @GuardedBy("stateLock");
-    private long sslNativePointer;
+    private final SslWrapper ssl;
 
     /**
      * Protected by synchronizing on stateLock. Starts as null, set by
@@ -102,47 +102,64 @@
      */
     private OpenSSLKey channelIdPrivateKey;
 
-    /** Set during startHandshake. */
-    private AbstractOpenSSLSession sslSession;
-
-    /** Used during handshake callbacks. */
-    private AbstractOpenSSLSession handshakeSession;
+    private final ActiveSession sslSession;
 
     private int writeTimeoutMilliseconds = 0;
     private int handshakeTimeoutMilliseconds = -1; // -1 = same as timeout; 0 = infinite
 
     ConscryptFileDescriptorSocket(SSLParametersImpl sslParameters) throws IOException {
         this.sslParameters = sslParameters;
+        this.ssl = newSsl(sslParameters, this);
+        sslSession = new ActiveSession(ssl, sslParameters.getSessionContext());
     }
 
     ConscryptFileDescriptorSocket(String hostname, int port, SSLParametersImpl sslParameters)
             throws IOException {
         super(hostname, port);
         this.sslParameters = sslParameters;
+        this.ssl = newSsl(sslParameters, this);
+        sslSession = new ActiveSession(ssl, sslParameters.getSessionContext());
     }
 
     ConscryptFileDescriptorSocket(InetAddress address, int port, SSLParametersImpl sslParameters)
             throws IOException {
         super(address, port);
         this.sslParameters = sslParameters;
+        this.ssl = newSsl(sslParameters, this);
+        sslSession = new ActiveSession(ssl, sslParameters.getSessionContext());
     }
 
     ConscryptFileDescriptorSocket(String hostname, int port, InetAddress clientAddress,
             int clientPort, SSLParametersImpl sslParameters) throws IOException {
         super(hostname, port, clientAddress, clientPort);
         this.sslParameters = sslParameters;
+        this.ssl = newSsl(sslParameters, this);
+        sslSession = new ActiveSession(ssl, sslParameters.getSessionContext());
     }
 
     ConscryptFileDescriptorSocket(InetAddress address, int port, InetAddress clientAddress,
             int clientPort, SSLParametersImpl sslParameters) throws IOException {
         super(address, port, clientAddress, clientPort);
         this.sslParameters = sslParameters;
+        this.ssl = newSsl(sslParameters, this);
+        sslSession = new ActiveSession(ssl, sslParameters.getSessionContext());
     }
 
     ConscryptFileDescriptorSocket(Socket socket, String hostname, int port, boolean autoClose,
             SSLParametersImpl sslParameters) throws IOException {
         super(socket, hostname, port, autoClose);
         this.sslParameters = sslParameters;
+        this.ssl = newSsl(sslParameters, this);
+        sslSession = new ActiveSession(ssl, sslParameters.getSessionContext());
+    }
+
+    private static SslWrapper newSsl(SSLParametersImpl sslParameters,
+            ConscryptFileDescriptorSocket engine) {
+        try {
+            return SslWrapper.newInstance(sslParameters, engine, engine, engine);
+        } catch (SSLException e) {
+            throw new RuntimeException(e);
+        }
     }
 
     /**
@@ -165,49 +182,23 @@
             }
         }
 
-        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);
-            }
+            // Prepare the SSL object for the handshake.
+            ssl.initialize(getHostname(), channelIdPrivateKey);
 
-            // 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);
+            // For clients, offer to resume a previously cached session to avoid the
+            // full TLS handshake.
+            if (getUseClientMode()) {
+                SslSessionWrapper cachedSession = clientSessionContext().getCachedSession(
+                        getHostnameOrIP(), getPort(), sslParameters);
+                if (cachedSession != null) {
+                    cachedSession.offerToResume(ssl);
                 }
             }
 
-            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();
@@ -222,11 +213,8 @@
                 }
             }
 
-            long sslSessionNativePointer;
             try {
-                NativeCrypto.SSL_do_handshake(
-                        sslNativePointer, Platform.getFileDescriptor(socket), this, getSoTimeout());
-                sslSessionNativePointer = NativeCrypto.SSL_get1_session(sslNativePointer);
+                ssl.doHandshake(Platform.getFileDescriptor(socket), getSoTimeout());
             } catch (CertificateException e) {
                 SSLHandshakeException wrapper = new SSLHandshakeException(e.getMessage());
                 wrapper.initCause(e);
@@ -249,43 +237,32 @@
                 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());
+                    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) {
+                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) {
+                } else {
                     state = STATE_READY;
                 }
 
@@ -296,8 +273,7 @@
                 }
             }
         } catch (SSLProtocolException e) {
-            throw (SSLHandshakeException) new SSLHandshakeException("Handshake failed")
-                    .initCause(e);
+            throw(SSLHandshakeException) new SSLHandshakeException("Handshake failed").initCause(e);
         } finally {
             // on exceptional exit, treat the socket as closed
             if (releaseResources) {
@@ -324,70 +300,83 @@
     @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);
+        ssl.chooseClientCertificate(keyTypeBytes, asn1DerEncodedPrincipals);
     }
 
     @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);
+        return ssl.clientPSKKeyRequested(identityHint, identity, key);
     }
 
     @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);
+        return ssl.serverPSKKeyRequested(identityHint, identity, key);
     }
 
     @Override
     @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks / info_callback
     public void onSSLStateChange(int type, int val) {
         if (type != NativeConstants.SSL_CB_HANDSHAKE_DONE) {
+            // We only care about successful completion.
             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.
+        // The handshake has completed successfully ...
 
-                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) {
+        // Update the session from the current state of the SSL object.
+        sslSession.onSessionEstablished(getHostnameOrIP(), getPort());
+
+        // First, update the state.
+        synchronized (stateLock) {
+            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;
+        }
+
+        // Let listeners know we are finally done
+        notifyHandshakeCompletedListeners();
+
+        synchronized (stateLock) {
             // Notify all threads waiting for the handshake to complete.
             stateLock.notifyAll();
         }
     }
 
+    @Override
+    @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks / new_session_callback
+    public void onNewSessionEstablished(long sslSessionNativePtr) {
+        try {
+            // Increment the reference count to "take ownership" of the session resource.
+            NativeCrypto.SSL_SESSION_up_ref(sslSessionNativePtr);
+
+            // Create a native reference which will release the SSL_SESSION in its finalizer.
+            // This constructor will only throw if the native pointer passed in is NULL, which
+            // BoringSSL guarantees will not happen.
+            NativeRef.SSL_SESSION ref = new SSL_SESSION(sslSessionNativePtr);
+
+            SslSessionWrapper sessionWrapper = SslSessionWrapper.newInstance(ref, sslSession);
+
+            // Cache the newly established session.
+            AbstractSessionContext ctx = sessionContext();
+            ctx.cacheSession(sessionWrapper);
+        } catch (Exception ignored) {
+            // Ignore.
+        }
+    }
+
+    @Override
+    public long serverSessionRequested(byte[] id) {
+        // TODO(nathanmittler): Implement server-side caching for TLS < 1.3
+        return 0;
+    }
+
     @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks
     @Override
     public void verifyCertificateChain(long[] certRefs, String authMethod)
@@ -403,16 +392,10 @@
             OpenSSLX509Certificate[] peerCertChain =
                     OpenSSLX509Certificate.createCertChain(certRefs);
 
-            byte[] ocspData = NativeCrypto.SSL_get_ocsp_response(sslNativePointer);
-            byte[] tlsSctData = NativeCrypto.SSL_get_signed_cert_timestamp_list(sslNativePointer);
+            // Update the peer information on the session.
+            sslSession.onPeerCertificatesReceived(getHostnameOrIP(), getPort(), peerCertChain);
 
-            // 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) {
+            if (getUseClientMode()) {
                 Platform.checkServerTrusted(x509tm, peerCertChain, authMethod, this);
             } else {
                 String authType = peerCertChain[0].getPublicKey().getAlgorithm();
@@ -422,9 +405,6 @@
             throw e;
         } catch (Exception e) {
             throw new CertificateException(e);
-        } finally {
-            // Clear this before notifying handshake completed listeners
-            handshakeSession = null;
         }
     }
 
@@ -553,11 +533,13 @@
                         throw new SocketException("socket is closed");
                     }
 
-                    if (DBG_STATE) assertReadableOrWriteableState();
+                    if (DBG_STATE) {
+                        assertReadableOrWriteableState();
+                    }
                 }
 
-                int ret = NativeCrypto.SSL_read(sslNativePointer, Platform.getFileDescriptor(socket),
-                        ConscryptFileDescriptorSocket.this, buf, offset, byteCount, getSoTimeout());
+                int ret =  ssl.read(
+                        Platform.getFileDescriptor(socket), buf, offset, byteCount, getSoTimeout());
                 if (ret == -1) {
                     synchronized (stateLock) {
                         if (state == STATE_CLOSED) {
@@ -572,11 +554,13 @@
         void awaitPendingOps() {
             if (DBG_STATE) {
                 synchronized (stateLock) {
-                    if (state != STATE_CLOSED) throw new AssertionError("State is: " + state);
+                    if (state != STATE_CLOSED) {
+                        throw new AssertionError("State is: " + state);
+                    }
                 }
             }
 
-            synchronized (readLock) { }
+            synchronized (readLock) {}
         }
     }
 
@@ -626,11 +610,12 @@
                         throw new SocketException("socket is closed");
                     }
 
-                    if (DBG_STATE) assertReadableOrWriteableState();
+                    if (DBG_STATE) {
+                        assertReadableOrWriteableState();
+                    }
                 }
 
-                NativeCrypto.SSL_write(sslNativePointer, Platform.getFileDescriptor(socket),
-                        ConscryptFileDescriptorSocket.this, buf, offset, byteCount,
+                ssl.write(Platform.getFileDescriptor(socket), buf, offset, byteCount,
                         writeTimeoutMilliseconds);
 
                 synchronized (stateLock) {
@@ -644,7 +629,9 @@
         void awaitPendingOps() {
             if (DBG_STATE) {
                 synchronized (stateLock) {
-                    if (state != STATE_CLOSED) throw new AssertionError("State is: " + state);
+                    if (state != STATE_CLOSED) {
+                        throw new AssertionError("State is: " + state);
+                    }
                 }
             }
 
@@ -654,23 +641,25 @@
 
     @Override
     public SSLSession getSession() {
-        if (sslSession == null) {
-            boolean handshakeCompleted = false;
+        boolean handshakeCompleted = false;
+        synchronized (stateLock) {
             try {
-                if (isConnected()) {
+                handshakeCompleted = state >= STATE_READY;
+                if (!handshakeCompleted && 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();
-            }
         }
+
+        if (!handshakeCompleted) {
+            // return an invalid session with
+            // invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
+            return SSLNullSession.getNullSession();
+        }
+
         return Platform.wrapSSLSession(sslSession);
     }
 
@@ -681,7 +670,9 @@
 
     @Override
     public SSLSession getHandshakeSession() {
-        return handshakeSession;
+        synchronized (stateLock) {
+            return state >= STATE_HANDSHAKE_STARTED && state < STATE_READY ? sslSession : null;
+        }
     }
 
     @Override
@@ -791,7 +782,7 @@
                         "Channel ID is only available after handshake completes");
             }
         }
-        return NativeCrypto.SSL_get_tls_channel_id(sslNativePointer);
+        return ssl.getTlsChannelId();
     }
 
     /**
@@ -938,7 +929,7 @@
                 // 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);
+                ssl.interrupt();
 
                 stateLock.notifyAll();
                 return;
@@ -953,7 +944,7 @@
 
         // Don't bother interrupting unless we have something to interrupt.
         if (sslInputStream != null || sslOutputStream != null) {
-            NativeCrypto.SSL_interrupt(sslNativePointer);
+            ssl.interrupt();
         }
 
         // Wait for the input and output streams to finish any reads they have in
@@ -972,8 +963,7 @@
     private void shutdownAndFreeSslNative() throws IOException {
         try {
             Platform.blockGuardOnNetwork();
-            NativeCrypto.SSL_shutdown(sslNativePointer, Platform.getFileDescriptor(socket),
-                    this);
+            ssl.shutdown(Platform.getFileDescriptor(socket));
         } catch (IOException ignored) {
             /*
              * Note that although close() can throw
@@ -992,12 +982,10 @@
     }
 
     private void free() {
-        if (sslNativePointer == 0) {
-            return;
+        if (!ssl.isClosed()) {
+            ssl.close();
+            Platform.closeGuardClose(guard);
         }
-        NativeCrypto.SSL_free(sslNativePointer);
-        sslNativePointer = 0;
-        Platform.closeGuardClose(guard);
     }
 
     @Override
@@ -1034,7 +1022,7 @@
      */
     @Override
     public byte[] getAlpnSelectedProtocol() {
-        return NativeCrypto.SSL_get0_alpn_selected(sslNativePointer);
+        return ssl.getAlpnSelectedProtocol();
     }
 
     /**
@@ -1103,4 +1091,12 @@
     public SecretKey getPSKKey(PSKKeyManager keyManager, String identityHint, String identity) {
         return keyManager.getKey(identityHint, identity, this);
     }
+
+    private ClientSessionContext clientSessionContext() {
+        return sslParameters.getClientSessionContext();
+    }
+
+    private AbstractSessionContext sessionContext() {
+        return sslParameters.getSessionContext();
+    }
 }
diff --git a/common/src/main/java/org/conscrypt/OpenSSLExtendedSessionImpl.java b/common/src/main/java/org/conscrypt/DelegatingExtendedSSLSession.java
similarity index 81%
rename from common/src/main/java/org/conscrypt/OpenSSLExtendedSessionImpl.java
rename to common/src/main/java/org/conscrypt/DelegatingExtendedSSLSession.java
index dead13d..e0114fd 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLExtendedSessionImpl.java
+++ b/common/src/main/java/org/conscrypt/DelegatingExtendedSSLSession.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 The Android Open Source Project
+ * Copyright 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,44 +30,46 @@
  * Implementation of the ExtendedSSLSession class for OpenSSL. Uses a delegate to maintain backward
  * compatibility with previous versions of Android which don't have ExtendedSSLSession.
  */
-final class OpenSSLExtendedSessionImpl extends ExtendedSSLSession {
-    private final AbstractOpenSSLSession delegate;
+final class DelegatingExtendedSSLSession extends ExtendedSSLSession {
+    // TODO: use BoringSSL API to actually fetch the real data
+    private static final String[] LOCAL_SUPPORTED_SIGNATURE_ALGORITHMS = new String[] {
+            "SHA512withRSA",
+            "SHA512withECDSA",
+            "SHA384withRSA",
+            "SHA384withECDSA",
+            "SHA256withRSA",
+            "SHA256withECDSA",
+            "SHA224withRSA",
+            "SHA224withECDSA",
+            "SHA1withRSA",
+            "SHA1withECDSA",
+    };
+    // TODO: use BoringSSL API to actually fetch the real data
+    private static final String[] PEER_SUPPORTED_SIGNATURE_ALGORITHMS = new String[] {
+            "SHA1withRSA",
+            "SHA1withECDSA"
+    };
 
-    OpenSSLExtendedSessionImpl(AbstractOpenSSLSession delegate) {
+    private final ActiveSession delegate;
+
+    DelegatingExtendedSSLSession(ActiveSession delegate) {
         this.delegate = delegate;
     }
 
-    AbstractOpenSSLSession getDelegate() {
+    ActiveSession getDelegate() {
         return delegate;
     }
 
     /* @Override */
     @SuppressWarnings("MissingOverride") // For Android backward-compatibility.
     public String[] getLocalSupportedSignatureAlgorithms() {
-        // From src/ssl/t1_lib.c tls12_sigalgs
-        // TODO: use BoringSSL API to actually fetch the real data
-        return new String[] {
-                "SHA512withRSA",
-                "SHA512withECDSA",
-                "SHA384withRSA",
-                "SHA384withECDSA",
-                "SHA256withRSA",
-                "SHA256withECDSA",
-                "SHA224withRSA",
-                "SHA224withECDSA",
-                "SHA1withRSA",
-                "SHA1withECDSA",
-        };
+        return LOCAL_SUPPORTED_SIGNATURE_ALGORITHMS.clone();
     }
 
     /* @Override */
     @SuppressWarnings("MissingOverride") // For Android backward-compatibility.
     public String[] getPeerSupportedSignatureAlgorithms() {
-        // TODO: use BoringSSL API to actually fetch the real data
-        return new String[] {
-                "SHA1withRSA",
-                "SHA1withECDSA",
-        };
+        return PEER_SUPPORTED_SIGNATURE_ALGORITHMS.clone();
     }
 
     /* @Override */
diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java
index e0ad406..6cd9f4d 100644
--- a/common/src/main/java/org/conscrypt/NativeCrypto.java
+++ b/common/src/main/java/org/conscrypt/NativeCrypto.java
@@ -64,8 +64,6 @@
     static native long EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q,
             byte[] dmp1, byte[] dmq1, byte[] iqmp);
 
-    static native int EVP_PKEY_size(NativeRef.EVP_PKEY pkey);
-
     static native int EVP_PKEY_type(NativeRef.EVP_PKEY pkey);
 
     static native String EVP_PKEY_print_public(NativeRef.EVP_PKEY pkeyRef);
@@ -118,10 +116,6 @@
      */
     static native byte[][] get_RSA_private_params(NativeRef.EVP_PKEY rsa);
 
-    static native byte[] i2d_RSAPublicKey(NativeRef.EVP_PKEY rsa);
-
-    static native byte[] i2d_RSAPrivateKey(NativeRef.EVP_PKEY rsa);
-
     // --- EC functions --------------------------
 
     static native long EVP_PKEY_new_EC_KEY(
@@ -180,8 +174,6 @@
 
     static native int EVP_MD_size(long evp_md_const);
 
-    static native int EVP_MD_block_size(long evp_md_const);
-
     // --- Message digest context functions --------------
 
     static native long EVP_MD_CTX_create();
@@ -294,8 +286,6 @@
 
     static native int EVP_AEAD_nonce_length(long evpAead);
 
-    static native int EVP_AEAD_max_tag_len(long evpAead);
-
     static native int EVP_AEAD_CTX_seal(long evpAead, byte[] key, int tagLengthInBytes, byte[] out,
             int outOffset, byte[] nonce, byte[] in, int inOffset, int inLength, byte[] ad)
             throws BadPaddingException;
@@ -322,14 +312,6 @@
 
     static native void RAND_bytes(byte[] output);
 
-    // --- ASN.1 objects -------------------------------------------------------
-
-    static native int OBJ_txt2nid(String oid);
-
-    static native String OBJ_txt2nid_longName(String oid);
-
-    static native String OBJ_txt2nid_oid(String oid);
-
     // --- X509_NAME -----------------------------------------------------------
 
     static int X509_NAME_hash(X500Principal principal) {
@@ -350,8 +332,6 @@
         }
     }
 
-    static native String X509_NAME_print_ex(long x509nameCtx, long flags);
-
     // --- X509 ----------------------------------------------------------------
 
     /** Used to request get_X509_GENERAL_NAME_stack get the "altname" field. */
@@ -542,11 +522,6 @@
 
     static native long create_BIO_OutputStream(OutputStream os);
 
-    static native int BIO_read(long bioRef, byte[] buffer);
-
-    static native void BIO_write(long bioRef, byte[] buffer, int offset, int length)
-            throws IOException;
-
     static native void BIO_free_all(long bioRef);
 
     // --- SSL handling --------------------------------------------------------
@@ -782,6 +757,8 @@
 
     static native void SSL_CTX_set_session_id_context(long ssl_ctx, byte[] sid_ctx);
 
+    static native long SSL_CTX_set_timeout(long ssl_ctx, long seconds);
+
     static native long SSL_new(long ssl_ctx) throws SSLException;
 
     static native void SSL_enable_tls_channel_id(long ssl) throws SSLException;
@@ -798,14 +775,8 @@
 
     static native void SSL_set_client_CA_list(long ssl, byte[][] asn1DerEncodedX500Principals);
 
-    static native long SSL_get_mode(long ssl);
-
     static native long SSL_set_mode(long ssl, long mode);
 
-    static native long SSL_clear_mode(long ssl, long mode);
-
-    static native long SSL_get_options(long ssl);
-
     static native long SSL_set_options(long ssl, long options);
 
     static native long SSL_clear_options(long ssl, long options);
@@ -830,25 +801,29 @@
 
     /** Protocols to enable by default when "TLSv1.2" is requested. */
     static final String[] TLSV12_PROTOCOLS = new String[] {
-            SUPPORTED_PROTOCOL_TLSV1, SUPPORTED_PROTOCOL_TLSV1_1, SUPPORTED_PROTOCOL_TLSV1_2,
+            SUPPORTED_PROTOCOL_TLSV1,
+            SUPPORTED_PROTOCOL_TLSV1_1,
+            SUPPORTED_PROTOCOL_TLSV1_2,
     };
 
     /** Protocols to enable by default when "TLSv1.1" is requested. */
     static final String[] TLSV11_PROTOCOLS = new String[] {
-            SUPPORTED_PROTOCOL_TLSV1, SUPPORTED_PROTOCOL_TLSV1_1, SUPPORTED_PROTOCOL_TLSV1_2,
+            SUPPORTED_PROTOCOL_TLSV1,
+            SUPPORTED_PROTOCOL_TLSV1_1,
+            SUPPORTED_PROTOCOL_TLSV1_2,
     };
 
     /** Protocols to enable by default when "TLSv1" is requested. */
     static final String[] TLSV1_PROTOCOLS = new String[] {
-            SUPPORTED_PROTOCOL_TLSV1, SUPPORTED_PROTOCOL_TLSV1_1, SUPPORTED_PROTOCOL_TLSV1_2,
+            SUPPORTED_PROTOCOL_TLSV1,
+            SUPPORTED_PROTOCOL_TLSV1_1,
+            SUPPORTED_PROTOCOL_TLSV1_2,
     };
 
     static final String[] DEFAULT_PROTOCOLS = TLSV12_PROTOCOLS;
 
     static String[] getSupportedProtocols() {
-        return new String[] {
-                SUPPORTED_PROTOCOL_TLSV1, SUPPORTED_PROTOCOL_TLSV1_1, SUPPORTED_PROTOCOL_TLSV1_2,
-        };
+        return TLSV12_PROTOCOLS.clone();
     }
 
     static void setEnabledProtocols(long ssl, String[] protocols) {
@@ -996,11 +971,9 @@
             long sslNativePointer, FileDescriptor fd, SSLHandshakeCallbacks shc, int timeoutMillis)
             throws SSLException, SocketTimeoutException, CertificateException;
 
-    /**
-     * Currently only intended for forcing renegotiation for testing.
-     * Not used within OpenSSLSocketImpl.
-     */
-    static native void SSL_renegotiate(long sslNativePointer) throws SSLException;
+    public static native String SSL_get_current_cipher(long sslNativePointer);
+
+    public static native String SSL_get_version(long sslNativePointer);
 
     /**
      * Returns the local X509 certificate references. Must X509_free when done.
@@ -1041,11 +1014,21 @@
 
     static native long SSL_SESSION_get_time(long sslSessionNativePointer);
 
+    static native long SSL_get_time(long sslNativePointer);
+
+    static native long SSL_set_timeout(long sslNativePointer, long millis);
+
+    static native long SSL_get_timeout(long sslNativePointer);
+
+    static native long SSL_SESSION_get_timeout(long sslSessionNativePointer);
+
+    static native byte[] SSL_session_id(long sslNativePointer);
+
     static native String SSL_SESSION_get_version(long sslSessionNativePointer);
 
     static native String SSL_SESSION_cipher(long sslSessionNativePointer);
 
-    static native String get_SSL_SESSION_tlsext_hostname(long sslSessionNativePointer);
+    static native void SSL_SESSION_up_ref(long sslSessionNativePointer);
 
     static native void SSL_SESSION_free(long sslSessionNativePointer);
 
@@ -1116,9 +1099,26 @@
          * Called when SSL state changes. This could be handshake completion.
          */
         void onSSLStateChange(int type, int val);
-    }
 
-    static native long ERR_peek_last_error();
+        /**
+         * Called when a new session has been established and may be added to the session cache.
+         * The callee is responsible for incrementing the reference count on the returned session.
+         */
+        void onNewSessionEstablished(long sslSessionNativePtr);
+
+        /**
+         * Called for servers where TLS < 1.3 (TLS 1.3 uses session tickets rather than
+         * application session caches).
+         *
+         * <p/>Looks up the session by ID in the application's session cache. If a valid session
+         * is returned, this callback is responsible for incrementing the reference count (and any
+         * required synchronization).
+         *
+         * @param id the ID of the session to find.
+         * @return the cached session or {@code 0} if no session was found matching the given ID.
+         */
+        long serverSessionRequested(byte[] id);
+    }
 
     static native String SSL_CIPHER_get_kx_name(long cipherAddress);
 
@@ -1150,10 +1150,6 @@
 
     static native int SSL_pending_written_bytes_in_BIO(long bio);
 
-    static native long SSL_get0_session(long ssl);
-
-    static native long SSL_get1_session(long ssl);
-
     /**
      * Returns the maximum overhead, in bytes, of sealing a record with SSL.
      */
@@ -1256,4 +1252,17 @@
      */
     static native void ENGINE_SSL_shutdown(long sslNativePointer, SSLHandshakeCallbacks shc)
             throws IOException;
+
+    /**
+     * Used for testing only.
+     */
+    static native int BIO_read(long bioRef, byte[] buffer);
+    static native void BIO_write(long bioRef, byte[] buffer, int offset, int length)
+            throws IOException;
+    static native long ERR_peek_last_error();
+    static native long SSL_clear_mode(long ssl, long mode);
+    static native long SSL_get_mode(long ssl);
+    static native long SSL_get_options(long ssl);
+    static native long SSL_get1_session(long ssl);
+    static native void SSL_renegotiate(long sslNativePointer) throws SSLException;
 }
diff --git a/common/src/main/java/org/conscrypt/NativeRef.java b/common/src/main/java/org/conscrypt/NativeRef.java
index ccc7cc0..62e9bd6 100644
--- a/common/src/main/java/org/conscrypt/NativeRef.java
+++ b/common/src/main/java/org/conscrypt/NativeRef.java
@@ -23,12 +23,12 @@
 abstract class NativeRef {
     final long context;
 
-    NativeRef(long ctx) {
-        if (ctx == 0) {
-            throw new NullPointerException("ctx == 0");
+    NativeRef(long context) {
+        if (context == 0) {
+            throw new NullPointerException("context == 0");
         }
 
-        this.context = ctx;
+        this.context = context;
     }
 
     @Override
@@ -45,108 +45,104 @@
         return (int) context;
     }
 
-    static class EC_GROUP extends NativeRef {
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (context != 0) {
+                doFree(context);
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    abstract void doFree(long context);
+
+    static final class EC_GROUP extends NativeRef {
         EC_GROUP(long ctx) {
             super(ctx);
         }
 
         @Override
-        protected void finalize() throws Throwable {
-            try {
-                NativeCrypto.EC_GROUP_clear_free(context);
-            } finally {
-                super.finalize();
-            }
+        void doFree(long context) {
+            NativeCrypto.EC_GROUP_clear_free(context);
         }
     }
 
-    static class EC_POINT extends NativeRef {
-        EC_POINT(long ctx) {
-            super(ctx);
+    static final class EC_POINT extends NativeRef {
+        EC_POINT(long nativePointer) {
+            super(nativePointer);
         }
 
         @Override
-        protected void finalize() throws Throwable {
-            try {
-                NativeCrypto.EC_POINT_clear_free(context);
-            } finally {
-                super.finalize();
-            }
+        void doFree(long context) {
+            NativeCrypto.EC_POINT_clear_free(context);
         }
     }
 
-    static class EVP_CIPHER_CTX extends NativeRef {
-        EVP_CIPHER_CTX(long ctx) {
-            super(ctx);
+    static final class EVP_CIPHER_CTX extends NativeRef {
+        EVP_CIPHER_CTX(long nativePointer) {
+            super(nativePointer);
         }
 
         @Override
-        protected void finalize() throws Throwable {
-            try {
-                NativeCrypto.EVP_CIPHER_CTX_free(context);
-            } finally {
-                super.finalize();
-            }
+        void doFree(long context) {
+            NativeCrypto.EVP_CIPHER_CTX_free(context);
         }
     }
 
-    static class EVP_MD_CTX extends NativeRef {
-        EVP_MD_CTX(long ctx) {
-            super(ctx);
+    static final class EVP_MD_CTX extends NativeRef {
+        EVP_MD_CTX(long nativePointer) {
+            super(nativePointer);
         }
 
         @Override
-        protected void finalize() throws Throwable {
-            try {
-                NativeCrypto.EVP_MD_CTX_destroy(context);
-            } finally {
-                super.finalize();
-            }
+        void doFree(long context) {
+            NativeCrypto.EVP_MD_CTX_destroy(context);
         }
     }
 
-    static class EVP_PKEY extends NativeRef {
-        EVP_PKEY(long ctx) {
-            super(ctx);
+    static final class EVP_PKEY extends NativeRef {
+        EVP_PKEY(long nativePointer) {
+            super(nativePointer);
         }
 
         @Override
-        protected void finalize() throws Throwable {
-            try {
-                NativeCrypto.EVP_PKEY_free(context);
-            } finally {
-                super.finalize();
-            }
+        void doFree(long context) {
+            NativeCrypto.EVP_PKEY_free(context);
         }
     }
 
-    static class EVP_PKEY_CTX extends NativeRef {
-        EVP_PKEY_CTX(long ctx) {
-            super(ctx);
+    static final class EVP_PKEY_CTX extends NativeRef {
+        EVP_PKEY_CTX(long nativePointer) {
+            super(nativePointer);
         }
 
         @Override
-        protected void finalize() throws Throwable {
-            try {
-                NativeCrypto.EVP_PKEY_CTX_free(context);
-            } finally {
-                super.finalize();
-            }
+        void doFree(long context) {
+            NativeCrypto.EVP_PKEY_CTX_free(context);
         }
     }
 
-    static class HMAC_CTX extends NativeRef {
-        HMAC_CTX(long ctx) {
-            super(ctx);
+    static final class HMAC_CTX extends NativeRef {
+        HMAC_CTX(long nativePointer) {
+            super(nativePointer);
         }
 
         @Override
-        protected void finalize() throws Throwable {
-            try {
-                NativeCrypto.HMAC_CTX_free(context);
-            } finally {
-                super.finalize();
-            }
+        void doFree(long context) {
+            NativeCrypto.HMAC_CTX_free(context);
+        }
+    }
+
+    static final class SSL_SESSION extends NativeRef {
+        SSL_SESSION(long nativePointer) {
+            super(nativePointer);
+        }
+
+        @Override
+        void doFree(long context) {
+            NativeCrypto.SSL_SESSION_free(context);
         }
     }
 }
diff --git a/common/src/main/java/org/conscrypt/OpenSSLContextImpl.java b/common/src/main/java/org/conscrypt/OpenSSLContextImpl.java
index c1865a1..143f481 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLContextImpl.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLContextImpl.java
@@ -30,11 +30,12 @@
 /**
  * OpenSSL-backed SSLContext service provider interface.
  *
+ * <p>Public to allow contruction via the provider framework.
+ *
  * @hide
  */
 @Internal
 public abstract class OpenSSLContextImpl extends SSLContextSpi {
-
     /**
      * The default SSLContextImpl for use with
      * SSLContext.getInstance("Default"). Protected by the
@@ -97,8 +98,8 @@
     @Override
     public void engineInit(KeyManager[] kms, TrustManager[] tms, SecureRandom sr)
             throws KeyManagementException {
-        sslParameters = new SSLParametersImpl(kms, tms, sr, clientSessionContext,
-                serverSessionContext, algorithms);
+        sslParameters = new SSLParametersImpl(
+                kms, tms, sr, clientSessionContext, serverSessionContext, algorithms);
     }
 
     @Override
@@ -147,18 +148,27 @@
         return clientSessionContext;
     }
 
+    /**
+     * Public to allow construction via the provider framework.
+     */
     public static final class TLSv12 extends OpenSSLContextImpl {
         public TLSv12() {
             super(NativeCrypto.TLSV12_PROTOCOLS);
         }
     }
 
+    /**
+     * Public to allow construction via the provider framework.
+     */
     public static final class TLSv11 extends OpenSSLContextImpl {
         public TLSv11() {
             super(NativeCrypto.TLSV11_PROTOCOLS);
         }
     }
 
+    /**
+     * Public to allow construction via the provider framework.
+     */
     public static final class TLSv1 extends OpenSSLContextImpl {
         public TLSv1() {
             super(NativeCrypto.TLSV1_PROTOCOLS);
diff --git a/common/src/main/java/org/conscrypt/OpenSSLSessionImpl.java b/common/src/main/java/org/conscrypt/OpenSSLSessionImpl.java
deleted file mode 100644
index d94252b..0000000
--- a/common/src/main/java/org/conscrypt/OpenSSLSessionImpl.java
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright (C) 2007 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 java.io.IOException;
-import java.security.cert.X509Certificate;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSessionBindingEvent;
-import javax.net.ssl.SSLSessionBindingListener;
-
-/**
- * Implementation of the class OpenSSLSessionImpl
- * based on BoringSSL.
- */
-class OpenSSLSessionImpl extends AbstractOpenSSLSession {
-    private long creationTime = 0;
-    long lastAccessedTime = 0;
-    final X509Certificate[] localCertificates;
-    final X509Certificate[] peerCertificates;
-
-    private final Map<String, Object> values = new HashMap<String, Object>();
-    private byte[] peerCertificateOcspData;
-    private byte[] peerTlsSctData;
-    long sslSessionNativePointer;
-    private String peerHost;
-    private int peerPort = -1;
-    private String cipherSuite;
-    private String protocol;
-    private byte[] id;
-
-    /**
-     * Class constructor creates an SSL session context given the appropriate
-     * SSL parameters.
-     */
-    OpenSSLSessionImpl(long sslSessionNativePointer, X509Certificate[] localCertificates,
-            X509Certificate[] peerCertificates, byte[] peerCertificateOcspData,
-            byte[] peerTlsSctData, String peerHost, int peerPort,
-            AbstractSessionContext sessionContext) {
-        super(sessionContext);
-        this.sslSessionNativePointer = sslSessionNativePointer;
-        this.localCertificates = localCertificates;
-        this.peerCertificates = peerCertificates;
-        this.peerCertificateOcspData = peerCertificateOcspData;
-        this.peerTlsSctData = peerTlsSctData;
-        this.peerHost = peerHost;
-        this.peerPort = peerPort;
-    }
-
-    /**
-     * Constructs a session from a byte[] containing an SSL session serialized with DER encoding.
-     * This allows loading of a previously saved OpenSSLSessionImpl.
-     *
-     * @throws IOException if the serialized session data can not be parsed
-     */
-    OpenSSLSessionImpl(byte[] derData, String peerHost, int peerPort,
-            X509Certificate[] peerCertificates, byte[] peerCertificateOcspData,
-            byte[] peerTlsSctData, AbstractSessionContext sessionContext)
-            throws IOException {
-        this(NativeCrypto.d2i_SSL_SESSION(derData), null, peerCertificates,
-                peerCertificateOcspData, peerTlsSctData, peerHost, peerPort, sessionContext);
-    }
-
-    /**
-     * Gets the identifier of the actual SSL session
-     * @return array of sessions' identifiers.
-     */
-    @Override
-    public byte[] getId() {
-        if (id == null) {
-            resetId();
-        }
-        return id;
-    }
-
-    /**
-     * Reset the id field to the current value found in the native
-     * SSL_SESSION. It can change during the lifetime of the session
-     * because while a session is created during initial handshake,
-     * with handshake_cutthrough, the SSL_do_handshake may return
-     * before we have read the session ticket from the server side and
-     * therefore have computed no id based on the SHA of the ticket.
-     */
-    @Override
-    void resetId() {
-        id = NativeCrypto.SSL_SESSION_session_id(sslSessionNativePointer);
-    }
-
-    /**
-     * Get the session object in DER format. This allows saving the session
-     * data or sharing it with other processes.
-     */
-    public byte[] getEncoded() {
-        return NativeCrypto.i2d_SSL_SESSION(sslSessionNativePointer);
-    }
-
-    /**
-     * Gets the creation time of the SSL session.
-     * @return the session's creation time in milliseconds since the epoch
-     */
-    @Override
-    public long getCreationTime() {
-        if (creationTime == 0) {
-            creationTime = NativeCrypto.SSL_SESSION_get_time(sslSessionNativePointer);
-        }
-        return creationTime;
-    }
-
-    /**
-     * Returns the last time this concrete SSL session was accessed. Accessing
-     * here is to mean that a new connection with the same SSL context data was
-     * established.
-     *
-     * @return the session's last access time in milliseconds since the epoch
-     */
-    @Override
-    public long getLastAccessedTime() {
-        return (lastAccessedTime == 0) ? getCreationTime() : lastAccessedTime;
-    }
-
-    @Override
-    public void setLastAccessedTime(long accessTimeMillis) {
-        lastAccessedTime = accessTimeMillis;
-    }
-
-    @Override
-    protected X509Certificate[] getX509LocalCertificates() {
-        return localCertificates;
-    }
-
-    @Override
-    protected X509Certificate[] getX509PeerCertificates() throws SSLPeerUnverifiedException {
-        if (peerCertificates == null || peerCertificates.length == 0) {
-            throw new SSLPeerUnverifiedException("No peer certificates");
-        }
-        return peerCertificates;
-    }
-
-    /**
-     * The peer's host name used in this SSL session is returned. It is the host
-     * name of the client for the server; and that of the server for the client.
-     * It is not a reliable way to get a fully qualified host name: it is mainly
-     * used internally to implement links for a temporary cache of SSL sessions.
-     *
-     * @return the host name of the peer, or {@code null} if no information is
-     *         available.
-     */
-    @Override
-    public String getPeerHost() {
-        return peerHost;
-    }
-
-    /**
-     * Returns the peer's port number for the actual SSL session. It is the port
-     * number of the client for the server; and that of the server for the
-     * client. It is not a reliable way to get a peer's port number: it is
-     * mainly used internally to implement links for a temporary cache of SSL
-     * sessions.
-     *
-     * @return the peer's port number, or {@code -1} if no one is available.
-     */
-    @Override
-    public int getPeerPort() {
-        return peerPort;
-    }
-
-    /**
-     * Returns a string identifier of the crypto tools used in the actual SSL
-     * session. For example AES_256_WITH_MD5.
-     */
-    @Override
-    public String getCipherSuite() {
-        if (cipherSuite == null) {
-            String name = NativeCrypto.SSL_SESSION_cipher(sslSessionNativePointer);
-            cipherSuite = NativeCrypto.OPENSSL_TO_STANDARD_CIPHER_SUITES.get(name);
-            if (cipherSuite == null) {
-                cipherSuite = name;
-            }
-        }
-        return cipherSuite;
-    }
-
-    /**
-     * Returns the standard version name of the SSL protocol used in all
-     * connections pertaining to this SSL session.
-     */
-    @Override
-    public String getProtocol() {
-        if (protocol == null) {
-            protocol = NativeCrypto.SSL_SESSION_get_version(sslSessionNativePointer);
-        }
-        return protocol;
-    }
-
-    /**
-     * Returns the object which is bound to the the input parameter name.
-     * This name is a sort of link to the data of the SSL session's application
-     * layer, if any exists.
-     *
-     * @param name the name of the binding to find.
-     * @return the value bound to that name, or null if the binding does not
-     *         exist.
-     * @throws IllegalArgumentException if the argument is null.
-     */
-    @Override
-    public Object getValue(String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("name == null");
-        }
-        return values.get(name);
-    }
-
-    /**
-     * Returns an array with the names (sort of links) of all the data
-     * objects of the application layer bound into the SSL session.
-     *
-     * @return a non-null (possibly empty) array of names of the data objects
-     *         bound to this SSL session.
-     */
-    @Override
-    public String[] getValueNames() {
-        return values.keySet().toArray(new String[values.size()]);
-    }
-
-    /**
-     * A link (name) with the specified value object of the SSL session's
-     * application layer data is created or replaced. If the new (or existing)
-     * value object implements the <code>SSLSessionBindingListener</code>
-     * interface, that object will be notified in due course.
-     *
-     * @param name the name of the link (no null are
-     *            accepted!)
-     * @param value data object that shall be bound to
-     *            name.
-     * @throws IllegalArgumentException if one or both argument(s) is null.
-     */
-    @Override
-    public void putValue(String name, Object value) {
-        if (name == null || value == null) {
-            throw new IllegalArgumentException("name == null || value == null");
-        }
-        Object old = values.put(name, value);
-        if (value instanceof SSLSessionBindingListener) {
-            ((SSLSessionBindingListener) value)
-                    .valueBound(new SSLSessionBindingEvent(this, name));
-        }
-        if (old instanceof SSLSessionBindingListener) {
-            ((SSLSessionBindingListener) old)
-                    .valueUnbound(new SSLSessionBindingEvent(this, name));
-        }
-    }
-
-    /**
-     * Removes a link (name) with the specified value object of the SSL
-     * session's application layer data.
-     *
-     * <p>If the value object implements the <code>SSLSessionBindingListener</code>
-     * interface, the object will receive a <code>valueUnbound</code> notification.
-     *
-     * @param name the name of the link (no null are
-     *            accepted!)
-     * @throws IllegalArgumentException if the argument is null.
-     */
-    @Override
-    public void removeValue(String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("name == null");
-        }
-        Object old = values.remove(name);
-        if (old instanceof SSLSessionBindingListener) {
-            SSLSessionBindingListener listener = (SSLSessionBindingListener) old;
-            listener.valueUnbound(new SSLSessionBindingEvent(this, name));
-        }
-    }
-
-    /**
-     * Returns the name requested by the SNI extension.
-     */
-    @Override
-    public String getRequestedServerName() {
-        return NativeCrypto.get_SSL_SESSION_tlsext_hostname(sslSessionNativePointer);
-    }
-
-    /**
-     * Returns the OCSP stapled response.
-     */
-    @Override
-    public List<byte[]> getStatusResponses() {
-        if (peerCertificateOcspData == null) {
-            return Collections.<byte[]>emptyList();
-        }
-
-        return Collections.singletonList(peerCertificateOcspData.clone());
-    }
-
-    @Override
-    public byte[] getTlsSctData() {
-        if (peerTlsSctData == null) {
-            return null;
-        }
-        return peerTlsSctData.clone();
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            // The constructor can throw an exception if this object is constructed from invalid
-            // saved session data.
-            if (sslSessionNativePointer != 0) {
-                NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);
-            }
-        } finally {
-            super.finalize();
-        }
-    }
-}
diff --git a/common/src/main/java/org/conscrypt/SSLNullSession.java b/common/src/main/java/org/conscrypt/SSLNullSession.java
index 88a0a9c..93650d6 100644
--- a/common/src/main/java/org/conscrypt/SSLNullSession.java
+++ b/common/src/main/java/org/conscrypt/SSLNullSession.java
@@ -28,6 +28,7 @@
  * SSLSocket#getSession()} before {@link SSLSocket#startHandshake()} is called.
  */
 final class SSLNullSession implements SSLSession, Cloneable {
+    static final String INVALID_CIPHER = "SSL_NULL_WITH_NULL_NULL";
 
     /*
      * Holds default instances so class preloading doesn't create an instance of
@@ -46,7 +47,11 @@
         return DefaultHolder.NULL_SESSION;
     }
 
-    SSLNullSession() {
+    static boolean isNullSession(SSLSession session) {
+        return session == DefaultHolder.NULL_SESSION;
+    }
+
+    private SSLNullSession() {
         creationTime = System.currentTimeMillis();
         lastAccessedTime = creationTime;
     }
@@ -58,7 +63,7 @@
 
     @Override
     public String getCipherSuite() {
-        return "SSL_NULL_WITH_NULL_NULL";
+        return INVALID_CIPHER;
     }
 
     @Override
diff --git a/common/src/main/java/org/conscrypt/SSLParametersImpl.java b/common/src/main/java/org/conscrypt/SSLParametersImpl.java
index 939401e..33e4c78 100644
--- a/common/src/main/java/org/conscrypt/SSLParametersImpl.java
+++ b/common/src/main/java/org/conscrypt/SSLParametersImpl.java
@@ -17,29 +17,17 @@
 
 package org.conscrypt;
 
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.InvalidKeyException;
 import java.security.KeyManagementException;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
 import java.security.SecureRandom;
 import java.security.UnrecoverableKeyException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
 import javax.crypto.SecretKey;
 import javax.net.ssl.KeyManager;
 import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.SSLSession;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.TrustManagerFactory;
 import javax.net.ssl.X509ExtendedKeyManager;
@@ -61,8 +49,6 @@
     private static volatile X509KeyManager defaultX509KeyManager;
     // default source of X.509 certificate based authentication trust decisions
     private static volatile X509TrustManager defaultX509TrustManager;
-    // default source of random numbers
-    private static volatile SecureRandom defaultSecureRandom;
     // default SSL parameters
     private static volatile SSLParametersImpl defaultParameters;
 
@@ -83,11 +69,11 @@
     private SecureRandom secureRandom;
 
     // protocols enabled for SSL connection
-    private String[] enabledProtocols;
+    String[] enabledProtocols;
     // set to indicate when obsolete protocols are filtered
-    private boolean isEnabledProtocolsFiltered;
+    boolean isEnabledProtocolsFiltered;
     // cipher suites enabled for SSL connection
-    private String[] enabledCipherSuites;
+    String[] enabledCipherSuites;
 
     // if the peer with this parameters tuned to work in client mode
     private boolean client_mode = true;
@@ -106,11 +92,11 @@
     private boolean ctVerificationEnabled;
 
     // server-side only. SCT and OCSP data to send to clients which request it
-    private byte[] sctExtension;
-    private byte[] ocspResponse;
+    byte[] sctExtension;
+    byte[] ocspResponse;
 
-    private byte[] alpnProtocols;
-    private boolean useSessionTickets;
+    byte[] alpnProtocols;
+    boolean useSessionTickets;
     private Boolean useSni;
 
     /**
@@ -189,13 +175,6 @@
     }
 
     /**
-     * @return server session context
-     */
-    ServerSessionContext getServerSessionContext() {
-        return serverSessionContext;
-    }
-
-    /**
      * @return client session context
      */
     ClientSessionContext getClientSessionContext() {
@@ -225,29 +204,6 @@
     }
 
     /**
-     * @return secure random
-     */
-    SecureRandom getSecureRandom() {
-        if (secureRandom != null) {
-            return secureRandom;
-        }
-        SecureRandom result = defaultSecureRandom;
-        if (result == null) {
-            // single-check idiom
-            defaultSecureRandom = result = new SecureRandom();
-        }
-        secureRandom = result;
-        return secureRandom;
-    }
-
-    /**
-     * @return the secure random member reference, even it is null
-     */
-    SecureRandom getSecureRandomMember() {
-        return secureRandom;
-    }
-
-    /**
      * @return the names of enabled cipher suites
      */
     String[] getEnabledCipherSuites() {
@@ -308,6 +264,10 @@
         this.alpnProtocols = alpnProtocols;
     }
 
+    byte[] getAlpnProtocols() {
+        return alpnProtocols;
+    }
+
     /**
      * Tunes the peer holding this parameters to work in client mode.
      * @param   mode if the peer is configured to work in client mode
@@ -383,7 +343,7 @@
      * extension Server Name Indication (SNI).
      */
     void setUseSni(boolean flag) {
-        useSni = Boolean.valueOf(flag);
+        useSni = flag;
     }
 
     /**
@@ -391,17 +351,26 @@
      * extension Server Name Indication (SNI).
      */
     boolean getUseSni() {
-        return useSni != null ? useSni.booleanValue() : isSniEnabledByDefault();
+        return useSni != null ? useSni : isSniEnabledByDefault();
     }
 
+    /**
+     * For testing only.
+     */
     void setCTVerificationEnabled(boolean enabled) {
         ctVerificationEnabled = enabled;
     }
 
+    /**
+     * For testing only.
+     */
     void setSCTExtension(byte[] extension) {
         sctExtension = extension;
     }
 
+    /**
+     * For testing only.
+     */
     void setOCSPResponse(byte[] response) {
         ocspResponse = response;
     }
@@ -410,111 +379,6 @@
         return ocspResponse;
     }
 
-    static byte[][] encodeIssuerX509Principals(X509Certificate[] certificates)
-            throws CertificateEncodingException {
-        byte[][] principalBytes = new byte[certificates.length][];
-        for (int i = 0; i < certificates.length; i++) {
-            principalBytes[i] = certificates[i].getIssuerX500Principal().getEncoded();
-        }
-        return principalBytes;
-    }
-
-    AbstractOpenSSLSession getSessionToReuse(long sslNativePointer, String hostname, int port)
-            throws SSLException {
-        OpenSSLSessionImpl sessionToReuse = null;
-
-        if (client_mode) {
-            // look for client session to reuse
-            SSLSession cachedSession = getCachedClientSession(clientSessionContext, hostname, port);
-            if (cachedSession != null) {
-                // The native pointer is used here, so we have to make sure it's not a delegate
-                // SSLSession class.
-                cachedSession = Platform.unwrapSSLSession(cachedSession);
-
-                if (cachedSession instanceof OpenSSLSessionImpl) {
-                    sessionToReuse = (OpenSSLSessionImpl) cachedSession;
-                    NativeCrypto.SSL_set_session(sslNativePointer,
-                            sessionToReuse.sslSessionNativePointer);
-                }
-            }
-        }
-
-        return sessionToReuse;
-    }
-
-    void setTlsChannelId(long sslNativePointer, OpenSSLKey channelIdPrivateKey)
-            throws SSLHandshakeException, SSLException {
-        // TLS Channel ID
-        if (channelIdEnabled) {
-            if (client_mode) {
-                // Client-side TLS Channel ID
-                if (channelIdPrivateKey == null) {
-                    throw new SSLHandshakeException("Invalid TLS channel ID key specified");
-                }
-                NativeCrypto.SSL_set1_tls_channel_id(sslNativePointer,
-                        channelIdPrivateKey.getNativeRef());
-            } else {
-                // Server-side TLS Channel ID
-                NativeCrypto.SSL_enable_tls_channel_id(sslNativePointer);
-            }
-        }
-    }
-
-    void setCertificate(long sslNativePointer, String alias) throws CertificateEncodingException,
-            SSLException {
-        if (alias == null) {
-            return;
-        }
-        X509KeyManager keyManager = getX509KeyManager();
-        if (keyManager == null) {
-            return;
-        }
-        PrivateKey privateKey = keyManager.getPrivateKey(alias);
-        if (privateKey == null) {
-            return;
-        }
-        X509Certificate[] certificates = keyManager.getCertificateChain(alias);
-        if (certificates == null) {
-            return;
-        }
-        PublicKey publicKey = (certificates.length > 0) ? certificates[0].getPublicKey() : null;
-
-        /*
-         * Make sure we keep a reference to the OpenSSLX509Certificate by using
-         * this array. Otherwise, if they're not OpenSSLX509Certificate
-         * instances originally, they may be garbage collected before we
-         * complete our JNI calls.
-         */
-        OpenSSLX509Certificate[] openSslCerts = new OpenSSLX509Certificate[certificates.length];
-        long[] x509refs = new long[certificates.length];
-        for (int i = 0; i < certificates.length; i++) {
-            OpenSSLX509Certificate openSslCert = OpenSSLX509Certificate
-                    .fromCertificate(certificates[i]);
-            openSslCerts[i] = openSslCert;
-            x509refs[i] = openSslCert.getContext();
-        }
-
-        // Note that OpenSSL says to use SSL_use_certificate before
-        // SSL_use_PrivateKey.
-        NativeCrypto.SSL_use_certificate(sslNativePointer, x509refs);
-
-        final OpenSSLKey key;
-        try {
-            key = OpenSSLKey.fromPrivateKeyForTLSStackOnly(privateKey, publicKey);
-            NativeCrypto.SSL_use_PrivateKey(sslNativePointer, key.getNativeRef());
-        } catch (InvalidKeyException e) {
-            throw new SSLException(e);
-        }
-
-        // We may not have access to all the information to check the private key
-        // if it's a wrapped platform key, so skip this check.
-        if (!key.isWrapped()) {
-            // Makes sure the set PrivateKey and X509Certificate refer to the same
-            // key by comparing the public values.
-            NativeCrypto.SSL_check_private_key(sslNativePointer);
-        }
-    }
-
     /**
      * This filters {@code obsoleteProtocol} from the list of {@code protocols}
      * down to help with app compatibility.
@@ -535,95 +399,6 @@
 
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
 
-    void setSSLParameters(long sslNativePointer, AliasChooser chooser, PSKCallbacks pskCallbacks,
-            String sniHostname) throws SSLException, IOException {
-        if (enabledProtocols.length == 0 && isEnabledProtocolsFiltered) {
-            throw new SSLHandshakeException("No enabled protocols; "
-                    + NativeCrypto.OBSOLETE_PROTOCOL_SSLV3
-                    + " is no longer supported and was filtered from the list");
-        }
-        NativeCrypto.SSL_configure_alpn(sslNativePointer, client_mode, alpnProtocols);
-        NativeCrypto.setEnabledProtocols(sslNativePointer, enabledProtocols);
-        NativeCrypto.setEnabledCipherSuites(sslNativePointer, enabledCipherSuites);
-
-        // setup server certificates and private keys.
-        // clients will receive a call back to request certificates.
-        if (!client_mode) {
-            Set<String> keyTypes = new HashSet<String>();
-            for (long sslCipherNativePointer : NativeCrypto.SSL_get_ciphers(sslNativePointer)) {
-                String keyType = getServerX509KeyType(sslCipherNativePointer);
-                if (keyType != null) {
-                    keyTypes.add(keyType);
-                }
-            }
-            X509KeyManager keyManager = getX509KeyManager();
-            if (keyManager != null) {
-                for (String keyType : keyTypes) {
-                    try {
-                        setCertificate(sslNativePointer,
-                                chooser.chooseServerAlias(x509KeyManager, keyType));
-                    } catch (CertificateEncodingException e) {
-                        throw new IOException(e);
-                    }
-                }
-            }
-
-            NativeCrypto.SSL_set_options(sslNativePointer,
-                    NativeConstants.SSL_OP_CIPHER_SERVER_PREFERENCE);
-
-            if (sctExtension != null) {
-                NativeCrypto.SSL_set_signed_cert_timestamp_list(sslNativePointer, sctExtension);
-            }
-
-            if (ocspResponse != null) {
-                NativeCrypto.SSL_set_ocsp_response(sslNativePointer, ocspResponse);
-            }
-        }
-
-        enablePSKKeyManagerIfRequested(sslNativePointer, pskCallbacks);
-
-        if (useSessionTickets) {
-            NativeCrypto.SSL_clear_options(sslNativePointer, NativeConstants.SSL_OP_NO_TICKET);
-        }
-        if (getUseSni() && AddressUtils.isValidSniHostname(sniHostname)) {
-            NativeCrypto.SSL_set_tlsext_host_name(sslNativePointer, sniHostname);
-        }
-
-        // BEAST attack mitigation (1/n-1 record splitting for CBC cipher suites
-        // with TLSv1 and SSLv3).
-        NativeCrypto.SSL_set_mode(sslNativePointer, NativeConstants.SSL_MODE_CBC_RECORD_SPLITTING);
-
-        boolean enableSessionCreation = getEnableSessionCreation();
-        if (!enableSessionCreation) {
-            NativeCrypto.SSL_set_session_creation_enabled(sslNativePointer, enableSessionCreation);
-        }
-    }
-
-    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
-    private void enablePSKKeyManagerIfRequested(long sslNativePointer, PSKCallbacks pskCallbacks)
-            throws SSLException {
-        // Enable Pre-Shared Key (PSK) key exchange if requested
-        PSKKeyManager pskKeyManager = getPSKKeyManager();
-        if (pskKeyManager != null) {
-            boolean pskEnabled = false;
-            for (String enabledCipherSuite : enabledCipherSuites) {
-                if ((enabledCipherSuite != null) && (enabledCipherSuite.contains("PSK"))) {
-                    pskEnabled = true;
-                    break;
-                }
-            }
-            if (pskEnabled) {
-                if (client_mode) {
-                    NativeCrypto.set_SSL_psk_client_callback_enabled(sslNativePointer, true);
-                } else {
-                    NativeCrypto.set_SSL_psk_server_callback_enabled(sslNativePointer, true);
-                    String identityHint = pskCallbacks.chooseServerPSKIdentityHint(pskKeyManager);
-                    NativeCrypto.SSL_use_psk_identity_hint(sslNativePointer, identityHint);
-                }
-            }
-        }
-    }
-
     /**
      * Returns whether Server Name Indication (SNI) is enabled by default for
      * sockets. For more information on SNI, see RFC 6066 section 3.
@@ -644,202 +419,6 @@
         }
     }
 
-    void setCertificateValidation(long sslNativePointer) throws IOException {
-        // setup peer certificate verification
-        if (!client_mode) {
-            // needing client auth takes priority...
-            boolean certRequested;
-            if (getNeedClientAuth()) {
-                NativeCrypto.SSL_set_verify(sslNativePointer,
-                                            NativeCrypto.SSL_VERIFY_PEER
-                                            | NativeCrypto.SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
-                certRequested = true;
-            // ... over just wanting it...
-            } else if (getWantClientAuth()) {
-                NativeCrypto.SSL_set_verify(sslNativePointer, NativeCrypto.SSL_VERIFY_PEER);
-                certRequested = true;
-            // ... and we must disable verification if we don't want client auth.
-            } else {
-                NativeCrypto.SSL_set_verify(sslNativePointer, NativeCrypto.SSL_VERIFY_NONE);
-                certRequested = false;
-            }
-
-            if (certRequested) {
-                X509TrustManager trustManager = getX509TrustManager();
-                X509Certificate[] issuers = trustManager.getAcceptedIssuers();
-                if (issuers != null && issuers.length != 0) {
-                    byte[][] issuersBytes;
-                    try {
-                        issuersBytes = encodeIssuerX509Principals(issuers);
-                    } catch (CertificateEncodingException e) {
-                        throw new IOException("Problem encoding principals", e);
-                    }
-                    NativeCrypto.SSL_set_client_CA_list(sslNativePointer, issuersBytes);
-                }
-            }
-        }
-    }
-
-    AbstractOpenSSLSession setupSession(long sslSessionNativePointer, long sslNativePointer,
-            final AbstractOpenSSLSession sessionToReuse, String hostname, int port,
-            boolean handshakeCompleted) throws IOException {
-        AbstractOpenSSLSession sslSession = null;
-        if (sessionToReuse != null && NativeCrypto.SSL_session_reused(sslNativePointer)) {
-            sslSession = sessionToReuse;
-            sslSession.setLastAccessedTime(System.currentTimeMillis());
-            NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);
-        } else {
-            if (!getEnableSessionCreation()) {
-                // Should have been prevented by
-                // NativeCrypto.SSL_set_session_creation_enabled
-                throw new IllegalStateException("SSL Session may not be created");
-            }
-            X509Certificate[] localCertificates = OpenSSLX509Certificate.createCertChain(
-                    NativeCrypto.SSL_get_certificate(sslNativePointer));
-            X509Certificate[] peerCertificates = OpenSSLX509Certificate.createCertChain(
-                    NativeCrypto.SSL_get_peer_cert_chain(sslNativePointer));
-            byte[] ocspData = NativeCrypto.SSL_get_ocsp_response(sslNativePointer);
-            byte[] tlsSctData = NativeCrypto.SSL_get_signed_cert_timestamp_list(sslNativePointer);
-            sslSession = new OpenSSLSessionImpl(sslSessionNativePointer, localCertificates,
-                    peerCertificates, ocspData, tlsSctData, hostname, port, getSessionContext());
-            // if not, putSession later in handshakeCompleted() callback
-            if (handshakeCompleted) {
-                getSessionContext().putSession(sslSession);
-            }
-        }
-        return sslSession;
-    }
-
-    void chooseClientCertificate(byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals,
-            long sslNativePointer, AliasChooser chooser) throws SSLException,
-            CertificateEncodingException {
-        Set<String> keyTypesSet = getSupportedClientKeyTypes(keyTypeBytes);
-        String[] keyTypes = keyTypesSet.toArray(new String[keyTypesSet.size()]);
-
-        X500Principal[] issuers;
-        if (asn1DerEncodedPrincipals == null) {
-            issuers = null;
-        } else {
-            issuers = new X500Principal[asn1DerEncodedPrincipals.length];
-            for (int i = 0; i < asn1DerEncodedPrincipals.length; i++) {
-                issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]);
-            }
-        }
-        X509KeyManager keyManager = getX509KeyManager();
-        String alias = (keyManager != null) ? chooser.chooseClientAlias(keyManager, issuers,
-                keyTypes) : null;
-        setCertificate(sslNativePointer, alias);
-    }
-
-    /**
-     * @see NativeCrypto.SSLHandshakeCallbacks#clientPSKKeyRequested(String, byte[], byte[])
-     */
-    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
-    int clientPSKKeyRequested(
-            String identityHint, byte[] identityBytesOut, byte[] key, PSKCallbacks pskCallbacks) {
-        PSKKeyManager pskKeyManager = getPSKKeyManager();
-        if (pskKeyManager == null) {
-            return 0;
-        }
-
-        String identity = pskCallbacks.chooseClientPSKIdentity(pskKeyManager, identityHint);
-        // Store identity in NULL-terminated modified UTF-8 representation into ientityBytesOut
-        byte[] identityBytes;
-        if (identity == null) {
-            identity = "";
-            identityBytes = EmptyArray.BYTE;
-        } else if (identity.isEmpty()) {
-            identityBytes = EmptyArray.BYTE;
-        } else {
-            try {
-                identityBytes = identity.getBytes("UTF-8");
-            } catch (UnsupportedEncodingException e) {
-                throw new RuntimeException("UTF-8 encoding not supported", e);
-            }
-        }
-        if (identityBytes.length + 1 > identityBytesOut.length) {
-            // Insufficient space in the output buffer
-            return 0;
-        }
-        if (identityBytes.length > 0) {
-            System.arraycopy(identityBytes, 0, identityBytesOut, 0, identityBytes.length);
-        }
-        identityBytesOut[identityBytes.length] = 0;
-
-        SecretKey secretKey = pskCallbacks.getPSKKey(pskKeyManager, identityHint, identity);
-        byte[] secretKeyBytes = secretKey.getEncoded();
-        if (secretKeyBytes == null) {
-            return 0;
-        } else if (secretKeyBytes.length > key.length) {
-            // Insufficient space in the output buffer
-            return 0;
-        }
-        System.arraycopy(secretKeyBytes, 0, key, 0, secretKeyBytes.length);
-        return secretKeyBytes.length;
-    }
-
-    /**
-     * @see NativeCrypto.SSLHandshakeCallbacks#serverPSKKeyRequested(String, String, byte[])
-     */
-    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
-    int serverPSKKeyRequested(
-            String identityHint, String identity, byte[] key, PSKCallbacks pskCallbacks) {
-        PSKKeyManager pskKeyManager = getPSKKeyManager();
-        if (pskKeyManager == null) {
-            return 0;
-        }
-        SecretKey secretKey = pskCallbacks.getPSKKey(pskKeyManager, identityHint, identity);
-        byte[] secretKeyBytes = secretKey.getEncoded();
-        if (secretKeyBytes == null) {
-            return 0;
-        } else if (secretKeyBytes.length > key.length) {
-            return 0;
-        }
-        System.arraycopy(secretKeyBytes, 0, key, 0, secretKeyBytes.length);
-        return secretKeyBytes.length;
-    }
-
-    /**
-     * Gets the suitable session reference from the session cache container.
-     */
-    SSLSession getCachedClientSession(ClientSessionContext sessionContext, String hostName,
-            int port) {
-        if (hostName == null) {
-            return null;
-        }
-
-        SSLSession session = sessionContext.getSession(hostName, port);
-        if (session == null) {
-            return null;
-        }
-
-        String protocol = session.getProtocol();
-        boolean protocolFound = false;
-        for (String enabledProtocol : enabledProtocols) {
-            if (protocol.equals(enabledProtocol)) {
-                protocolFound = true;
-                break;
-            }
-        }
-        if (!protocolFound) {
-            return null;
-        }
-
-        String cipherSuite = session.getCipherSuite();
-        boolean cipherSuiteFound = false;
-        for (String enabledCipherSuite : enabledCipherSuites) {
-            if (cipherSuite.equals(enabledCipherSuite)) {
-                cipherSuiteFound = true;
-                break;
-            }
-        }
-        if (!cipherSuiteFound) {
-            return null;
-        }
-
-        return session;
-    }
-
     /**
      * For abstracting the X509KeyManager calls between
      * {@link X509KeyManager#chooseClientAlias(String[], java.security.Principal[], java.net.Socket)}
@@ -1004,93 +583,6 @@
         this.useCipherSuitesOrder = useCipherSuitesOrder;
     }
 
-    /** Key type: RSA certificate. */
-    private static final String KEY_TYPE_RSA = "RSA";
-
-    /** Key type: Diffie-Hellman certificate signed by issuer with RSA signature. */
-    private static final String KEY_TYPE_DH_RSA = "DH_RSA";
-
-    /** Key type: Elliptic Curve certificate. */
-    private static final String KEY_TYPE_EC = "EC";
-
-    /** Key type: Elliptic Curve certificate signed by issuer with ECDSA signature. */
-    private static final String KEY_TYPE_EC_EC = "EC_EC";
-
-    /** Key type: Elliptic Curve certificate signed by issuer with RSA signature. */
-    private static final String KEY_TYPE_EC_RSA = "EC_RSA";
-
-    /**
-     * Returns key type constant suitable for calling X509KeyManager.chooseServerAlias or
-     * X509ExtendedKeyManager.chooseEngineServerAlias. Returns {@code null} for key exchanges that
-     * do not use X.509 for server authentication.
-     */
-    private static String getServerX509KeyType(long sslCipherNative) throws SSLException {
-        String kx_name = NativeCrypto.SSL_CIPHER_get_kx_name(sslCipherNative);
-        if (kx_name.equals("RSA") || kx_name.equals("DHE_RSA") || kx_name.equals("ECDHE_RSA")) {
-            return KEY_TYPE_RSA;
-        } else if (kx_name.equals("ECDHE_ECDSA")) {
-            return KEY_TYPE_EC;
-        } else if (kx_name.equals("ECDH_RSA")) {
-            return KEY_TYPE_EC_RSA;
-        } else if (kx_name.equals("ECDH_ECDSA")) {
-            return KEY_TYPE_EC_EC;
-        } else if (kx_name.equals("DH_RSA")) {
-            return KEY_TYPE_DH_RSA;
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Similar to getServerKeyType, but returns value given TLS
-     * ClientCertificateType byte values from a CertificateRequest
-     * message for use with X509KeyManager.chooseClientAlias or
-     * X509ExtendedKeyManager.chooseEngineClientAlias.
-     * <p>
-     * Visible for testing.
-     */
-    static String getClientKeyType(byte clientCertificateType) {
-        // See also http://www.ietf.org/assignments/tls-parameters/tls-parameters.xml
-        switch (clientCertificateType) {
-            case NativeConstants.TLS_CT_RSA_SIGN:
-                return KEY_TYPE_RSA; // RFC rsa_sign
-            case NativeConstants.TLS_CT_RSA_FIXED_DH:
-                return KEY_TYPE_DH_RSA; // RFC rsa_fixed_dh
-            case NativeConstants.TLS_CT_ECDSA_SIGN:
-                return KEY_TYPE_EC; // RFC ecdsa_sign
-            case NativeConstants.TLS_CT_RSA_FIXED_ECDH:
-                return KEY_TYPE_EC_RSA; // RFC rsa_fixed_ecdh
-            case NativeConstants.TLS_CT_ECDSA_FIXED_ECDH:
-                return KEY_TYPE_EC_EC; // RFC ecdsa_fixed_ecdh
-            default:
-                return null;
-        }
-    }
-
-    /**
-     * Gets the supported key types for client certificates based on the
-     * {@code ClientCertificateType} values provided by the server.
-     *
-     * @param clientCertificateTypes {@code ClientCertificateType} values provided by the server.
-     *        See https://www.ietf.org/assignments/tls-parameters/tls-parameters.xml.
-     * @return supported key types that can be used in {@code X509KeyManager.chooseClientAlias} and
-     *         {@code X509ExtendedKeyManager.chooseEngineClientAlias}.
-     *
-     * Visible for testing.
-     */
-    static Set<String> getSupportedClientKeyTypes(byte[] clientCertificateTypes) {
-        Set<String> result = new HashSet<String>(clientCertificateTypes.length);
-        for (byte keyTypeCode : clientCertificateTypes) {
-            String keyType = getClientKeyType(keyTypeCode);
-            if (keyType == null) {
-                // Unsupported client key type -- ignore
-                continue;
-            }
-            result.add(keyType);
-        }
-        return result;
-    }
-
     private static String[] getDefaultCipherSuites(
             boolean x509CipherSuitesNeeded,
             boolean pskCipherSuitesNeeded) {
diff --git a/common/src/main/java/org/conscrypt/SSLUtils.java b/common/src/main/java/org/conscrypt/SSLUtils.java
index 74e6cc5..806d0fb 100644
--- a/common/src/main/java/org/conscrypt/SSLUtils.java
+++ b/common/src/main/java/org/conscrypt/SSLUtils.java
@@ -41,8 +41,14 @@
 import static org.conscrypt.NativeConstants.SSL3_RT_MAX_PACKET_SIZE;
 
 import java.nio.ByteBuffer;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.HashSet;
+import java.util.Set;
 import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.security.cert.CertificateException;
 
 /**
  * Utility methods for SSL packet processing. Copied from the Netty project.
@@ -54,6 +60,35 @@
             System.getProperty("org.conscrypt.useEngineSocketByDefault", "false"));
     private static final int MAX_PROTOCOL_LENGTH = 255;
 
+    // TODO(nathanmittler): Should these be in NativeConstants?
+    enum SessionType {
+        /**
+         * Identifies OpenSSL sessions.
+         */
+        OPEN_SSL(1),
+
+        /**
+         * Identifies OpenSSL sessions with OCSP stapled data.
+         */
+        OPEN_SSL_WITH_OCSP(2),
+
+        /**
+         * Identifies OpenSSL sessions with TLS SCT data.
+         */
+        OPEN_SSL_WITH_TLS_SCT(3);
+
+        SessionType(int value) {
+            this.value = value;
+        }
+
+        static final boolean isSupportedType(int type) {
+            return type == OPEN_SSL.value || type == OPEN_SSL_WITH_OCSP.value
+                    || type == OPEN_SSL_WITH_TLS_SCT.value;
+        }
+
+        final int value;
+    }
+
     /**
      * States for SSL engines.
      */
@@ -130,6 +165,135 @@
     private static final int MAX_ENCRYPTION_OVERHEAD_DIFF =
             Integer.MAX_VALUE - MAX_ENCRYPTION_OVERHEAD_LENGTH;
 
+    /** Key type: RSA certificate. */
+    private static final String KEY_TYPE_RSA = "RSA";
+
+    /** Key type: Diffie-Hellman certificate signed by issuer with RSA signature. */
+    private static final String KEY_TYPE_DH_RSA = "DH_RSA";
+
+    /** Key type: Elliptic Curve certificate. */
+    private static final String KEY_TYPE_EC = "EC";
+
+    /** Key type: Elliptic Curve certificate signed by issuer with ECDSA signature. */
+    private static final String KEY_TYPE_EC_EC = "EC_EC";
+
+    /** Key type: Elliptic Curve certificate signed by issuer with RSA signature. */
+    private static final String KEY_TYPE_EC_RSA = "EC_RSA";
+
+    /**
+     * Returns key type constant suitable for calling X509KeyManager.chooseServerAlias or
+     * X509ExtendedKeyManager.chooseEngineServerAlias. Returns {@code null} for key exchanges that
+     * do not use X.509 for server authentication.
+     */
+    static String getServerX509KeyType(long sslCipherNative) throws SSLException {
+        String kx_name = NativeCrypto.SSL_CIPHER_get_kx_name(sslCipherNative);
+        if (kx_name.equals("RSA") || kx_name.equals("DHE_RSA") || kx_name.equals("ECDHE_RSA")) {
+            return KEY_TYPE_RSA;
+        } else if (kx_name.equals("ECDHE_ECDSA")) {
+            return KEY_TYPE_EC;
+        } else if (kx_name.equals("ECDH_RSA")) {
+            return KEY_TYPE_EC_RSA;
+        } else if (kx_name.equals("ECDH_ECDSA")) {
+            return KEY_TYPE_EC_EC;
+        } else if (kx_name.equals("DH_RSA")) {
+            return KEY_TYPE_DH_RSA;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Similar to getServerKeyType, but returns value given TLS
+     * ClientCertificateType byte values from a CertificateRequest
+     * message for use with X509KeyManager.chooseClientAlias or
+     * X509ExtendedKeyManager.chooseEngineClientAlias.
+     * <p>
+     * Visible for testing.
+     */
+    static String getClientKeyType(byte clientCertificateType) {
+        // See also http://www.ietf.org/assignments/tls-parameters/tls-parameters.xml
+        switch (clientCertificateType) {
+            case NativeConstants.TLS_CT_RSA_SIGN:
+                return KEY_TYPE_RSA; // RFC rsa_sign
+            case NativeConstants.TLS_CT_RSA_FIXED_DH:
+                return KEY_TYPE_DH_RSA; // RFC rsa_fixed_dh
+            case NativeConstants.TLS_CT_ECDSA_SIGN:
+                return KEY_TYPE_EC; // RFC ecdsa_sign
+            case NativeConstants.TLS_CT_RSA_FIXED_ECDH:
+                return KEY_TYPE_EC_RSA; // RFC rsa_fixed_ecdh
+            case NativeConstants.TLS_CT_ECDSA_FIXED_ECDH:
+                return KEY_TYPE_EC_EC; // RFC ecdsa_fixed_ecdh
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * Gets the supported key types for client certificates based on the
+     * {@code ClientCertificateType} values provided by the server.
+     *
+     * @param clientCertificateTypes {@code ClientCertificateType} values provided by the server.
+     *        See https://www.ietf.org/assignments/tls-parameters/tls-parameters.xml.
+     * @return supported key types that can be used in {@code X509KeyManager.chooseClientAlias} and
+     *         {@code X509ExtendedKeyManager.chooseEngineClientAlias}.
+     *
+     * Visible for testing.
+     */
+    static Set<String> getSupportedClientKeyTypes(byte[] clientCertificateTypes) {
+        Set<String> result = new HashSet<String>(clientCertificateTypes.length);
+        for (byte keyTypeCode : clientCertificateTypes) {
+            String keyType = SSLUtils.getClientKeyType(keyTypeCode);
+            if (keyType == null) {
+                // Unsupported client key type -- ignore
+                continue;
+            }
+            result.add(keyType);
+        }
+        return result;
+    }
+
+    static byte[][] encodeIssuerX509Principals(X509Certificate[] certificates)
+            throws CertificateEncodingException {
+        byte[][] principalBytes = new byte[certificates.length][];
+        for (int i = 0; i < certificates.length; i++) {
+            principalBytes[i] = certificates[i].getIssuerX500Principal().getEncoded();
+        }
+        return principalBytes;
+    }
+
+    static String getCipherSuiteFromName(String name) {
+        String cipherSuite = name;
+        if (name != null) {
+            cipherSuite = NativeCrypto.OPENSSL_TO_STANDARD_CIPHER_SUITES.get(name);
+        }
+        return cipherSuite != null ? cipherSuite : SSLNullSession.INVALID_CIPHER;
+    }
+
+    /**
+     * Converts the peer certificates into a cert chain.
+     */
+    static javax.security.cert.X509Certificate[] toCertificateChain(X509Certificate[] certificates)
+            throws SSLPeerUnverifiedException {
+        try {
+            javax.security.cert.X509Certificate[] chain =
+                    new javax.security.cert.X509Certificate[certificates.length];
+
+            for (int i = 0; i < certificates.length; i++) {
+                byte[] encoded = certificates[i].getEncoded();
+                chain[i] = javax.security.cert.X509Certificate.getInstance(encoded);
+            }
+            return chain;
+        } catch (CertificateEncodingException e) {
+            SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
+            exception.initCause(exception);
+            throw exception;
+        } catch (CertificateException e) {
+            SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
+            exception.initCause(exception);
+            throw exception;
+        }
+    }
+
     /**
      * Calculates the minimum bytes required in the encrypted output buffer for the given number of
      * plaintext source bytes.
diff --git a/common/src/main/java/org/conscrypt/ServerSessionContext.java b/common/src/main/java/org/conscrypt/ServerSessionContext.java
index 20fc3ae..dc4cafb 100644
--- a/common/src/main/java/org/conscrypt/ServerSessionContext.java
+++ b/common/src/main/java/org/conscrypt/ServerSessionContext.java
@@ -16,7 +16,7 @@
 
 package org.conscrypt;
 
-import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLContext;
 
 /**
  * Caches server sessions. Indexes by session ID. Users typically look up
@@ -26,10 +26,9 @@
  */
 @Internal
 public final class ServerSessionContext extends AbstractSessionContext {
-
     private SSLServerSessionCache persistentCache;
 
-    public ServerSessionContext() {
+    ServerSessionContext() {
         super(100);
 
         // TODO make sure SSL_CTX does not automaticaly clear sessions we want it to cache
@@ -48,30 +47,23 @@
         NativeCrypto.SSL_CTX_set_session_id_context(sslCtxNativePointer, new byte[] { ' ' });
     }
 
+    /**
+     * Applications should not use this method. Instead use {@link
+     * Conscrypt.Contexts#setServerSessionCache(SSLContext, SSLServerSessionCache)}.
+     */
     public void setPersistentCache(SSLServerSessionCache persistentCache) {
         this.persistentCache = persistentCache;
     }
 
     @Override
-    protected void sessionRemoved(SSLSession session) {}
-
-    @Override
-    public SSLSession getSession(byte[] sessionId) {
-        // First see if AbstractSessionContext can satisfy the request.
-        SSLSession cachedSession = super.getSession(sessionId);
-        if (cachedSession != null) {
-            // This will already have gone through Platform#wrapSSLSession
-            return cachedSession;
-        }
-
-        // Then check the persistent cache.
+    SslSessionWrapper getSessionFromPersistentCache(byte[] sessionId) {
         if (persistentCache != null) {
             byte[] data = persistentCache.getSessionData(sessionId);
             if (data != null) {
-                OpenSSLSessionImpl session = toSession(data, null, -1);
+                SslSessionWrapper session = SslSessionWrapper.newInstance(this, data, null, -1);
                 if (session != null && session.isValid()) {
-                    super.putSession(session);
-                    return Platform.wrapSSLSession(session);
+                    cacheSession(session);
+                    return session;
                 }
             }
         }
@@ -80,15 +72,18 @@
     }
 
     @Override
-    void putSession(SSLSession session) {
-        super.putSession(session);
-
-        // TODO: In background thread.
+    void onBeforeAddSession(SslSessionWrapper session) {
+        // TODO: Do this in background thread.
         if (persistentCache != null) {
-            byte[] data = toBytes(session);
+            byte[] data = session.toBytes();
             if (data != null) {
-                persistentCache.putSessionData(session, data);
+                persistentCache.putSessionData(session.toSSLSession(), data);
             }
         }
     }
+
+    @Override
+    void onBeforeRemoveSession(SslSessionWrapper session) {
+        // Do nothing.
+    }
 }
diff --git a/common/src/main/java/org/conscrypt/SslSessionWrapper.java b/common/src/main/java/org/conscrypt/SslSessionWrapper.java
new file mode 100644
index 0000000..497df1c
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/SslSessionWrapper.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License", "www.google.com", 443);
+ * 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.SessionType.OPEN_SSL_WITH_OCSP;
+import static org.conscrypt.SSLUtils.SessionType.OPEN_SSL_WITH_TLS_SCT;
+import static org.conscrypt.SSLUtils.SessionType.isSupportedType;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.util.List;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionContext;
+import javax.security.cert.X509Certificate;
+
+/**
+ * A utility wrapper that abstracts operations on the underlying native SSL_SESSION instance.
+ *
+ * This is abstract only to support mocking for tests.
+ */
+abstract class SslSessionWrapper {
+    /**
+     * Creates a new instance. Since BoringSSL does not provide an API to get access to all
+     * session information via the SSL_SESSION, we get some values (e.g. peer certs) from
+     * the active session instead (i.e. the SSL object).
+     */
+    static SslSessionWrapper newInstance(NativeRef.SSL_SESSION ref, ActiveSession activeSession)
+            throws SSLPeerUnverifiedException {
+        AbstractSessionContext context = (AbstractSessionContext) activeSession.getSessionContext();
+        if (context instanceof ClientSessionContext) {
+            return new Impl(context, ref, activeSession.getPeerHost(),
+                    activeSession.getPeerPort(), activeSession.getPeerCertificates(),
+                    getOcspResponse(activeSession),
+                    activeSession.getPeerSignedCertificateTimestamp());
+        }
+
+        // Server's will be cached by ID and won't have any of the extra fields.
+        return new Impl(context, ref, null, -1, null, null, null);
+    }
+
+    private static byte[] getOcspResponse(ActiveSession activeSession) {
+        List<byte[]> ocspResponseList = activeSession.getStatusResponses();
+        if (ocspResponseList.size() >= 1) {
+            return ocspResponseList.get(0);
+        }
+        return null;
+    }
+
+    /**
+     * Creates a new {@link SslSessionWrapper} instance from the provided serialized bytes, which
+     * were generated by {@link #toBytes()}.
+     *
+     * @return The new instance if successful. If unable to parse the bytes for any reason, returns
+     * {@code null}.
+     */
+    static SslSessionWrapper newInstance(
+            AbstractSessionContext context, byte[] data, String host, int port) {
+        ByteBuffer buf = ByteBuffer.wrap(data);
+        try {
+            int type = buf.getInt();
+            if (!isSupportedType(type)) {
+                throw new IOException("Unexpected type ID: " + type);
+            }
+
+            int length = buf.getInt();
+            checkRemaining(buf, length);
+
+            byte[] sessionData = new byte[length];
+            buf.get(sessionData);
+
+            int count = buf.getInt();
+            checkRemaining(buf, count);
+
+            java.security.cert.X509Certificate[] peerCerts =
+                    new java.security.cert.X509Certificate[count];
+            for (int i = 0; i < count; i++) {
+                length = buf.getInt();
+                checkRemaining(buf, length);
+
+                byte[] certData = new byte[length];
+                buf.get(certData);
+                try {
+                    peerCerts[i] = OpenSSLX509Certificate.fromX509Der(certData);
+                } catch (Exception e) {
+                    throw new IOException("Can not read certificate " + i + "/" + count);
+                }
+            }
+
+            byte[] ocspData = null;
+            if (type >= OPEN_SSL_WITH_OCSP.value) {
+                // We only support one OCSP response now, but in the future
+                // we may support RFC 6961 which has multiple.
+                int countOcspResponses = buf.getInt();
+                checkRemaining(buf, countOcspResponses);
+
+                if (countOcspResponses >= 1) {
+                    int ocspLength = buf.getInt();
+                    checkRemaining(buf, ocspLength);
+
+                    ocspData = new byte[ocspLength];
+                    buf.get(ocspData);
+
+                    // Skip the rest of the responses.
+                    for (int i = 1; i < countOcspResponses; i++) {
+                        ocspLength = buf.getInt();
+                        checkRemaining(buf, ocspLength);
+                        buf.position(buf.position() + ocspLength);
+                    }
+                }
+            }
+
+            byte[] tlsSctData = null;
+            if (type == OPEN_SSL_WITH_TLS_SCT.value) {
+                int tlsSctDataLength = buf.getInt();
+                checkRemaining(buf, tlsSctDataLength);
+
+                if (tlsSctDataLength > 0) {
+                    tlsSctData = new byte[tlsSctDataLength];
+                    buf.get(tlsSctData);
+                }
+            }
+
+            if (buf.remaining() != 0) {
+                log(new AssertionError("Read entire session, but data still remains; rejecting"));
+                return null;
+            }
+
+            NativeRef.SSL_SESSION ref =
+                    new NativeRef.SSL_SESSION(NativeCrypto.d2i_SSL_SESSION(sessionData));
+            return new Impl(context, ref, host, port, peerCerts, ocspData, tlsSctData);
+        } catch (IOException e) {
+            log(e);
+            return null;
+        } catch (BufferUnderflowException e) {
+            log(e);
+            return null;
+        }
+    }
+
+    abstract byte[] getId();
+
+    abstract boolean isValid();
+
+    abstract void offerToResume(SslWrapper ssl) throws SSLException;
+
+    abstract String getCipherSuite();
+
+    abstract String getProtocol();
+
+    abstract String getPeerHost();
+
+    abstract int getPeerPort();
+
+    /**
+     * Returns the OCSP stapled response. The returned array is not copied; the caller must
+     * either not modify the returned array or make a copy.
+     *
+     * @see <a href="https://tools.ietf.org/html/rfc6066">RFC 6066</a>
+     * @see <a href="https://tools.ietf.org/html/rfc6961">RFC 6961</a>
+     */
+    abstract byte[] getPeerOcspStapledResponse();
+
+    /**
+     * Returns the signed certificate timestamp (SCT) received from the peer. The returned array
+     * is not copied; the caller must either not modify the returned array or make a copy.
+     *
+     * @see <a href="https://tools.ietf.org/html/rfc6962">RFC 6962</a>
+     */
+    abstract byte[] getPeerSignedCertificateTimestamp();
+
+    /**
+     * Converts the given session to bytes.
+     *
+     * @return session data as bytes or null if the session can't be converted
+     */
+    abstract byte[] toBytes();
+
+    /**
+     * Converts this object to a {@link SSLSession}. The returned session will support only a
+     * subset of the {@link SSLSession} API.
+     */
+    abstract SSLSession toSSLSession();
+
+    /**
+     * The session wrapper implementation.
+     */
+    private static final class Impl extends SslSessionWrapper {
+        private final NativeRef.SSL_SESSION ref;
+
+        // BoringSSL offers no API to obtain these values directly from the SSL_SESSION.
+        private final AbstractSessionContext context;
+        private final String host;
+        private final int port;
+        private final String protocol;
+        private final String cipherSuite;
+        private final java.security.cert.X509Certificate[] peerCertificates;
+        private final byte[] peerOcspStapledResponse;
+        private final byte[] peerSignedCertificateTimestamp;
+
+        private Impl(AbstractSessionContext context, NativeRef.SSL_SESSION ref, String host,
+                int port, java.security.cert.X509Certificate[] peerCertificates,
+                byte[] peerOcspStapledResponse, byte[] peerSignedCertificateTimestamp) {
+            this.context = context;
+            this.host = host;
+            this.port = port;
+            this.peerCertificates = peerCertificates;
+            this.peerOcspStapledResponse = peerOcspStapledResponse;
+            this.peerSignedCertificateTimestamp = peerSignedCertificateTimestamp;
+            this.protocol = NativeCrypto.SSL_SESSION_get_version(ref.context);
+            this.cipherSuite = SSLUtils.getCipherSuiteFromName(
+                    NativeCrypto.SSL_SESSION_cipher(ref.context));
+            this.ref = ref;
+        }
+
+        @Override
+        byte[] getId() {
+            return NativeCrypto.SSL_SESSION_session_id(ref.context);
+        }
+
+        private long getCreationTime() {
+            return NativeCrypto.SSL_SESSION_get_time(ref.context);
+        }
+
+        @Override
+        boolean isValid() {
+            long creationTimeMillis = getCreationTime();
+            // Use the minimum of the timeout from the context and the session.
+            long timeoutMillis =
+                    Math.max(0,
+                            Math.min(context.getSessionTimeout(),
+                                    NativeCrypto.SSL_SESSION_get_timeout(ref.context)))
+                    * 1000;
+            return (System.currentTimeMillis() - timeoutMillis) < creationTimeMillis;
+        }
+
+        @Override
+        void offerToResume(SslWrapper ssl) throws SSLException {
+            ssl.offerToResumeSession(ref.context);
+        }
+
+        @Override
+        String getCipherSuite() {
+            return cipherSuite;
+        }
+
+        @Override
+        String getProtocol() {
+            return protocol;
+        }
+
+        @Override
+        String getPeerHost() {
+            return host;
+        }
+
+        @Override
+        int getPeerPort() {
+            return port;
+        }
+
+        @Override
+        byte[] getPeerOcspStapledResponse() {
+            return peerOcspStapledResponse;
+        }
+
+        @Override
+        byte[] getPeerSignedCertificateTimestamp() {
+            return peerSignedCertificateTimestamp;
+        }
+
+        @Override
+        byte[] toBytes() {
+            try {
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                DataOutputStream daos = new DataOutputStream(baos);
+
+                daos.writeInt(OPEN_SSL_WITH_TLS_SCT.value); // session type ID
+
+                // Session data.
+                byte[] data = NativeCrypto.i2d_SSL_SESSION(ref.context);
+                daos.writeInt(data.length);
+                daos.write(data);
+
+                // Certificates.
+                daos.writeInt(peerCertificates.length);
+
+                for (Certificate cert : peerCertificates) {
+                    data = cert.getEncoded();
+                    daos.writeInt(data.length);
+                    daos.write(data);
+                }
+
+                if (peerOcspStapledResponse != null) {
+                    daos.writeInt(1);
+                    daos.writeInt(peerOcspStapledResponse.length);
+                    daos.write(peerOcspStapledResponse);
+                } else {
+                    daos.writeInt(0);
+                }
+
+                if (peerSignedCertificateTimestamp != null) {
+                    daos.writeInt(peerSignedCertificateTimestamp.length);
+                    daos.write(peerSignedCertificateTimestamp);
+                } else {
+                    daos.writeInt(0);
+                }
+
+                // TODO: local certificates?
+
+                return baos.toByteArray();
+            } catch (IOException e) {
+                // TODO(nathanmittler): Better error handling?
+                System.err.println("Failed to convert saved SSL Session: " + e.getMessage());
+                return null;
+            } catch (CertificateEncodingException e) {
+                log(e);
+                return null;
+            }
+        }
+
+        @Override
+        SSLSession toSSLSession() {
+            return new SSLSession() {
+                @Override
+                public byte[] getId() {
+                    return Impl.this.getId();
+                }
+
+                @Override
+                public String getCipherSuite() {
+                    return Impl.this.getCipherSuite();
+                }
+
+                @Override
+                public String getProtocol() {
+                    return Impl.this.getProtocol();
+                }
+
+                @Override
+                public String getPeerHost() {
+                    return Impl.this.getPeerHost();
+                }
+
+                @Override
+                public int getPeerPort() {
+                    return Impl.this.getPeerPort();
+                }
+
+                @Override
+                public long getCreationTime() {
+                    return Impl.this.getCreationTime();
+                }
+
+                @Override
+                public boolean isValid() {
+                    return Impl.this.isValid();
+                }
+
+                // UNSUPPORTED OPERATIONS
+
+                @Override
+                public SSLSessionContext getSessionContext() {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public long getLastAccessedTime() {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void invalidate() {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void putValue(String s, Object o) {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public Object getValue(String s) {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public void removeValue(String s) {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public String[] getValueNames() {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public Certificate[] getLocalCertificates() {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public X509Certificate[] getPeerCertificateChain()
+                        throws SSLPeerUnverifiedException {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public Principal getLocalPrincipal() {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public int getPacketBufferSize() {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
+                public int getApplicationBufferSize() {
+                    throw new UnsupportedOperationException();
+                }
+            };
+        }
+    }
+
+    private static void log(Throwable t) {
+        // TODO(nathanmittler): Better error handling?
+        System.out.println("Error inflating SSL session: "
+                + (t.getMessage() != null ? t.getMessage() : t.getClass().getName()));
+    }
+
+    private static void checkRemaining(ByteBuffer buf, int length) throws IOException {
+        if (length < 0) {
+            throw new IOException("Length is negative: " + length);
+        }
+        if (length > buf.remaining()) {
+            throw new IOException(
+                    "Length of blob is longer than available: " + length + " > " + buf.remaining());
+        }
+    }
+}
diff --git a/common/src/main/java/org/conscrypt/SslWrapper.java b/common/src/main/java/org/conscrypt/SslWrapper.java
new file mode 100644
index 0000000..b396aa9
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/SslWrapper.java
@@ -0,0 +1,581 @@
+/*
+ * 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.NativeConstants.SSL_RECEIVED_SHUTDOWN;
+import static org.conscrypt.NativeConstants.SSL_SENT_SHUTDOWN;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.SocketTimeoutException;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.HashSet;
+import java.util.Set;
+import javax.crypto.SecretKey;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+import javax.security.auth.x500.X500Principal;
+import org.conscrypt.NativeCrypto.SSLHandshakeCallbacks;
+import org.conscrypt.SSLParametersImpl.AliasChooser;
+import org.conscrypt.SSLParametersImpl.PSKCallbacks;
+
+/**
+ * A utility wrapper that abstracts operations on the underlying native SSL instance.
+ */
+final class SslWrapper {
+    private final SSLParametersImpl parameters;
+    private final SSLHandshakeCallbacks handshakeCallbacks;
+    private final AliasChooser aliasChooser;
+    private final PSKCallbacks pskCallbacks;
+    private long ssl;
+
+    static SslWrapper newInstance(SSLParametersImpl parameters,
+            SSLHandshakeCallbacks handshakeCallbacks, AliasChooser chooser,
+            PSKCallbacks pskCallbacks) throws SSLException {
+        long ctx = parameters.getSessionContext().sslCtxNativePointer;
+        long ssl = NativeCrypto.SSL_new(ctx);
+        return new SslWrapper(ssl, parameters, handshakeCallbacks, chooser, pskCallbacks);
+    }
+
+    private SslWrapper(long ssl, SSLParametersImpl parameters,
+            SSLHandshakeCallbacks handshakeCallbacks, AliasChooser aliasChooser,
+            PSKCallbacks pskCallbacks) {
+        this.ssl = ssl;
+        this.parameters = parameters;
+        this.handshakeCallbacks = handshakeCallbacks;
+        this.aliasChooser = aliasChooser;
+        this.pskCallbacks = pskCallbacks;
+    }
+
+    long ssl() {
+        return ssl;
+    }
+
+    BioWrapper newBio() {
+        try {
+            return new BioWrapper();
+        } catch (SSLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    void offerToResumeSession(long sslSessionNativePointer) throws SSLException {
+        NativeCrypto.SSL_set_session(ssl, sslSessionNativePointer);
+    }
+
+    byte[] getSessionId() {
+        return NativeCrypto.SSL_session_id(ssl);
+    }
+
+    long getTime() {
+        return NativeCrypto.SSL_get_time(ssl);
+    }
+
+    long getTimeout() {
+        return NativeCrypto.SSL_get_timeout(ssl);
+    }
+
+    void setTimeout(long millis) {
+        NativeCrypto.SSL_set_timeout(ssl, millis);
+    }
+
+    String getCipherSuite() {
+        String name = NativeCrypto.SSL_get_current_cipher(ssl);
+        return SSLUtils.getCipherSuiteFromName(name);
+    }
+
+    OpenSSLX509Certificate[] getLocalCertificates() {
+        return OpenSSLX509Certificate.createCertChain(NativeCrypto.SSL_get_certificate(ssl));
+    }
+
+    OpenSSLX509Certificate[] getPeerCertificates() {
+        return OpenSSLX509Certificate.createCertChain(NativeCrypto.SSL_get_peer_cert_chain(ssl));
+    }
+
+    byte[] getPeerCertificateOcspData() {
+        return NativeCrypto.SSL_get_ocsp_response(ssl);
+    }
+
+    byte[] getPeerTlsSctData() {
+        return NativeCrypto.SSL_get_signed_cert_timestamp_list(ssl);
+    }
+
+    /**
+     * @see NativeCrypto.SSLHandshakeCallbacks#clientPSKKeyRequested(String, byte[], byte[])
+     */
+    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
+    int clientPSKKeyRequested(String identityHint, byte[] identityBytesOut, byte[] key) {
+        PSKKeyManager pskKeyManager = parameters.getPSKKeyManager();
+        if (pskKeyManager == null) {
+            return 0;
+        }
+
+        String identity = pskCallbacks.chooseClientPSKIdentity(pskKeyManager, identityHint);
+        // Store identity in NULL-terminated modified UTF-8 representation into ientityBytesOut
+        byte[] identityBytes;
+        if (identity == null) {
+            identity = "";
+            identityBytes = EmptyArray.BYTE;
+        } else if (identity.isEmpty()) {
+            identityBytes = EmptyArray.BYTE;
+        } else {
+            try {
+                identityBytes = identity.getBytes("UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                throw new RuntimeException("UTF-8 encoding not supported", e);
+            }
+        }
+        if (identityBytes.length + 1 > identityBytesOut.length) {
+            // Insufficient space in the output buffer
+            return 0;
+        }
+        if (identityBytes.length > 0) {
+            System.arraycopy(identityBytes, 0, identityBytesOut, 0, identityBytes.length);
+        }
+        identityBytesOut[identityBytes.length] = 0;
+
+        SecretKey secretKey = pskCallbacks.getPSKKey(pskKeyManager, identityHint, identity);
+        byte[] secretKeyBytes = secretKey.getEncoded();
+        if (secretKeyBytes == null) {
+            return 0;
+        } else if (secretKeyBytes.length > key.length) {
+            // Insufficient space in the output buffer
+            return 0;
+        }
+        System.arraycopy(secretKeyBytes, 0, key, 0, secretKeyBytes.length);
+        return secretKeyBytes.length;
+    }
+
+    /**
+     * @see NativeCrypto.SSLHandshakeCallbacks#serverPSKKeyRequested(String, String, byte[])
+     */
+    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
+    int serverPSKKeyRequested(String identityHint, String identity, byte[] key) {
+        PSKKeyManager pskKeyManager = parameters.getPSKKeyManager();
+        if (pskKeyManager == null) {
+            return 0;
+        }
+        SecretKey secretKey = pskCallbacks.getPSKKey(pskKeyManager, identityHint, identity);
+        byte[] secretKeyBytes = secretKey.getEncoded();
+        if (secretKeyBytes == null) {
+            return 0;
+        } else if (secretKeyBytes.length > key.length) {
+            return 0;
+        }
+        System.arraycopy(secretKeyBytes, 0, key, 0, secretKeyBytes.length);
+        return secretKeyBytes.length;
+    }
+
+    void chooseClientCertificate(byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals)
+            throws SSLException, CertificateEncodingException {
+        Set<String> keyTypesSet = SSLUtils.getSupportedClientKeyTypes(keyTypeBytes);
+        String[] keyTypes = keyTypesSet.toArray(new String[keyTypesSet.size()]);
+
+        X500Principal[] issuers;
+        if (asn1DerEncodedPrincipals == null) {
+            issuers = null;
+        } else {
+            issuers = new X500Principal[asn1DerEncodedPrincipals.length];
+            for (int i = 0; i < asn1DerEncodedPrincipals.length; i++) {
+                issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]);
+            }
+        }
+        X509KeyManager keyManager = parameters.getX509KeyManager();
+        String alias = (keyManager != null)
+                ? aliasChooser.chooseClientAlias(keyManager, issuers, keyTypes)
+                : null;
+        setCertificate(alias);
+    }
+
+    void setCertificate(String alias) throws CertificateEncodingException, SSLException {
+        if (alias == null) {
+            return;
+        }
+        X509KeyManager keyManager = parameters.getX509KeyManager();
+        if (keyManager == null) {
+            return;
+        }
+        PrivateKey privateKey = keyManager.getPrivateKey(alias);
+        if (privateKey == null) {
+            return;
+        }
+        X509Certificate[] certificates = keyManager.getCertificateChain(alias);
+        if (certificates == null) {
+            return;
+        }
+        PublicKey publicKey = (certificates.length > 0) ? certificates[0].getPublicKey() : null;
+
+        /*
+         * Make sure we keep a reference to the OpenSSLX509Certificate by using
+         * this array. Otherwise, if they're not OpenSSLX509Certificate
+         * instances originally, they may be garbage collected before we
+         * complete our JNI calls.
+         */
+        OpenSSLX509Certificate[] openSslCerts = new OpenSSLX509Certificate[certificates.length];
+        long[] x509refs = new long[certificates.length];
+        for (int i = 0; i < certificates.length; i++) {
+            OpenSSLX509Certificate openSslCert =
+                    OpenSSLX509Certificate.fromCertificate(certificates[i]);
+            openSslCerts[i] = openSslCert;
+            x509refs[i] = openSslCert.getContext();
+        }
+
+        // Note that OpenSSL says to use SSL_use_certificate before
+        // SSL_use_PrivateKey.
+        NativeCrypto.SSL_use_certificate(ssl, x509refs);
+
+        final OpenSSLKey key;
+        try {
+            key = OpenSSLKey.fromPrivateKeyForTLSStackOnly(privateKey, publicKey);
+            NativeCrypto.SSL_use_PrivateKey(ssl, key.getNativeRef());
+        } catch (InvalidKeyException e) {
+            throw new SSLException(e);
+        }
+
+        // We may not have access to all the information to check the private key
+        // if it's a wrapped platform key, so skip this check.
+        if (!key.isWrapped()) {
+            // Makes sure the set PrivateKey and X509Certificate refer to the same
+            // key by comparing the public values.
+            NativeCrypto.SSL_check_private_key(ssl);
+        }
+    }
+
+    String getVersion() {
+        return NativeCrypto.SSL_get_version(ssl);
+    }
+
+    boolean isReused() {
+        return NativeCrypto.SSL_session_reused(ssl);
+    }
+
+    String getRequestedServerName() {
+        return NativeCrypto.SSL_get_servername(ssl);
+    }
+
+    byte[] getTlsChannelId() throws SSLException {
+        return NativeCrypto.SSL_get_tls_channel_id(ssl);
+    }
+
+    void initialize(String hostname, OpenSSLKey channelIdPrivateKey) throws IOException {
+        boolean enableSessionCreation = parameters.getEnableSessionCreation();
+        if (!enableSessionCreation) {
+            NativeCrypto.SSL_set_session_creation_enabled(ssl, false);
+        }
+
+        // Allow servers to trigger renegotiation. Some inadvisable server
+        // configurations cause them to attempt to renegotiate during
+        // certain protocols.
+        NativeCrypto.SSL_accept_renegotiations(ssl);
+
+        if (isClient()) {
+            NativeCrypto.SSL_set_connect_state(ssl);
+
+            // Configure OCSP and CT extensions for client
+            NativeCrypto.SSL_enable_ocsp_stapling(ssl);
+            if (parameters.isCTVerificationEnabled(hostname)) {
+                NativeCrypto.SSL_enable_signed_cert_timestamps(ssl);
+            }
+        } else {
+            NativeCrypto.SSL_set_accept_state(ssl);
+
+            // Configure OCSP for server
+            if (parameters.getOCSPResponse() != null) {
+                NativeCrypto.SSL_enable_ocsp_stapling(ssl);
+            }
+        }
+
+        if (parameters.getEnabledProtocols().length == 0 && parameters.isEnabledProtocolsFiltered) {
+            throw new SSLHandshakeException("No enabled protocols; "
+                    + NativeCrypto.OBSOLETE_PROTOCOL_SSLV3
+                    + " is no longer supported and was filtered from the list");
+        }
+        NativeCrypto.SSL_configure_alpn(ssl, isClient(), parameters.alpnProtocols);
+        NativeCrypto.setEnabledProtocols(ssl, parameters.enabledProtocols);
+        NativeCrypto.setEnabledCipherSuites(ssl, parameters.enabledCipherSuites);
+
+        // setup server certificates and private keys.
+        // clients will receive a call back to request certificates.
+        if (!isClient()) {
+            Set<String> keyTypes = new HashSet<String>();
+            for (long sslCipherNativePointer : NativeCrypto.SSL_get_ciphers(ssl)) {
+                String keyType = SSLUtils.getServerX509KeyType(sslCipherNativePointer);
+                if (keyType != null) {
+                    keyTypes.add(keyType);
+                }
+            }
+            X509KeyManager keyManager = parameters.getX509KeyManager();
+            if (keyManager != null) {
+                for (String keyType : keyTypes) {
+                    try {
+                        setCertificate(aliasChooser.chooseServerAlias(keyManager, keyType));
+                    } catch (CertificateEncodingException e) {
+                        throw new IOException(e);
+                    }
+                }
+            }
+
+            NativeCrypto.SSL_set_options(ssl, NativeConstants.SSL_OP_CIPHER_SERVER_PREFERENCE);
+
+            if (parameters.sctExtension != null) {
+                NativeCrypto.SSL_set_signed_cert_timestamp_list(ssl, parameters.sctExtension);
+            }
+
+            if (parameters.ocspResponse != null) {
+                NativeCrypto.SSL_set_ocsp_response(ssl, parameters.ocspResponse);
+            }
+        }
+
+        enablePSKKeyManagerIfRequested();
+
+        if (parameters.useSessionTickets) {
+            NativeCrypto.SSL_clear_options(ssl, NativeConstants.SSL_OP_NO_TICKET);
+        } else {
+            NativeCrypto.SSL_set_options(
+                    ssl, NativeCrypto.SSL_get_options(ssl) | NativeConstants.SSL_OP_NO_TICKET);
+        }
+
+        if (parameters.getUseSni() && AddressUtils.isValidSniHostname(hostname)) {
+            NativeCrypto.SSL_set_tlsext_host_name(ssl, hostname);
+        }
+
+        // BEAST attack mitigation (1/n-1 record splitting for CBC cipher suites
+        // with TLSv1 and SSLv3).
+        NativeCrypto.SSL_set_mode(ssl, NativeConstants.SSL_MODE_CBC_RECORD_SPLITTING);
+
+        setCertificateValidation(ssl);
+        setTlsChannelId(channelIdPrivateKey);
+    }
+
+    // TODO(nathanmittler): Remove once after we switch to the engine socket.
+    void doHandshake(FileDescriptor fd, int timeoutMillis)
+            throws CertificateException, SocketTimeoutException, SSLException {
+        NativeCrypto.SSL_do_handshake(ssl, fd, handshakeCallbacks, timeoutMillis);
+    }
+
+    int doHandshake() throws IOException {
+        return NativeCrypto.ENGINE_SSL_do_handshake(ssl, handshakeCallbacks);
+    }
+
+    // TODO(nathanmittler): Remove once after we switch to the engine socket.
+    int read(FileDescriptor fd, byte[] buf, int offset, int len, int timeoutMillis)
+            throws IOException {
+        return NativeCrypto.SSL_read(ssl, fd, handshakeCallbacks, buf, offset, len, timeoutMillis);
+    }
+
+    // TODO(nathanmittler): Remove once after we switch to the engine socket.
+    void write(FileDescriptor fd, byte[] buf, int offset, int len, int timeoutMillis)
+            throws IOException {
+        NativeCrypto.SSL_write(ssl, fd, handshakeCallbacks, buf, offset, len, timeoutMillis);
+    }
+
+    @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package
+    private void enablePSKKeyManagerIfRequested() throws SSLException {
+        // Enable Pre-Shared Key (PSK) key exchange if requested
+        PSKKeyManager pskKeyManager = parameters.getPSKKeyManager();
+        if (pskKeyManager != null) {
+            boolean pskEnabled = false;
+            for (String enabledCipherSuite : parameters.enabledCipherSuites) {
+                if ((enabledCipherSuite != null) && (enabledCipherSuite.contains("PSK"))) {
+                    pskEnabled = true;
+                    break;
+                }
+            }
+            if (pskEnabled) {
+                if (isClient()) {
+                    NativeCrypto.set_SSL_psk_client_callback_enabled(ssl, true);
+                } else {
+                    NativeCrypto.set_SSL_psk_server_callback_enabled(ssl, true);
+                    String identityHint = pskCallbacks.chooseServerPSKIdentityHint(pskKeyManager);
+                    NativeCrypto.SSL_use_psk_identity_hint(ssl, identityHint);
+                }
+            }
+        }
+    }
+
+    private void setTlsChannelId(OpenSSLKey channelIdPrivateKey) throws SSLException {
+        if (!parameters.channelIdEnabled) {
+            return;
+        }
+
+        if (parameters.getUseClientMode()) {
+            // Client-side TLS Channel ID
+            if (channelIdPrivateKey == null) {
+                throw new SSLHandshakeException("Invalid TLS channel ID key specified");
+            }
+            NativeCrypto.SSL_set1_tls_channel_id(ssl, channelIdPrivateKey.getNativeRef());
+        } else {
+            // Server-side TLS Channel ID
+            NativeCrypto.SSL_enable_tls_channel_id(ssl);
+        }
+    }
+
+    private void setCertificateValidation(long sslNativePointer) throws SSLException {
+        // setup peer certificate verification
+        if (!isClient()) {
+            // needing client auth takes priority...
+            boolean certRequested;
+            if (parameters.getNeedClientAuth()) {
+                NativeCrypto.SSL_set_verify(sslNativePointer, NativeCrypto.SSL_VERIFY_PEER
+                                | NativeCrypto.SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
+                certRequested = true;
+                // ... over just wanting it...
+            } else if (parameters.getWantClientAuth()) {
+                NativeCrypto.SSL_set_verify(sslNativePointer, NativeCrypto.SSL_VERIFY_PEER);
+                certRequested = true;
+                // ... and we must disable verification if we don't want client auth.
+            } else {
+                NativeCrypto.SSL_set_verify(sslNativePointer, NativeCrypto.SSL_VERIFY_NONE);
+                certRequested = false;
+            }
+
+            if (certRequested) {
+                X509TrustManager trustManager = parameters.getX509TrustManager();
+                X509Certificate[] issuers = trustManager.getAcceptedIssuers();
+                if (issuers != null && issuers.length != 0) {
+                    byte[][] issuersBytes;
+                    try {
+                        issuersBytes = SSLUtils.encodeIssuerX509Principals(issuers);
+                    } catch (CertificateEncodingException e) {
+                        throw new SSLException("Problem encoding principals", e);
+                    }
+                    NativeCrypto.SSL_set_client_CA_list(sslNativePointer, issuersBytes);
+                }
+            }
+        }
+    }
+
+    void interrupt() {
+        NativeCrypto.SSL_interrupt(ssl);
+    }
+
+    // TODO(nathanmittler): Remove once after we switch to the engine socket.
+    void shutdown(FileDescriptor fd) throws IOException {
+        NativeCrypto.SSL_shutdown(ssl, fd, handshakeCallbacks);
+    }
+
+    void shutdown() throws IOException {
+        NativeCrypto.ENGINE_SSL_shutdown(ssl, handshakeCallbacks);
+    }
+
+    boolean wasShutdownReceived() {
+        return (NativeCrypto.SSL_get_shutdown(ssl) & SSL_RECEIVED_SHUTDOWN) != 0;
+    }
+
+    boolean wasShutdownSent() {
+        return (NativeCrypto.SSL_get_shutdown(ssl) & SSL_SENT_SHUTDOWN) != 0;
+    }
+
+    int readDirectByteBuffer(long destAddress, int destLength)
+            throws IOException, CertificateException {
+        return NativeCrypto.ENGINE_SSL_read_direct(
+                ssl, destAddress, destLength, handshakeCallbacks);
+    }
+
+    int readArray(byte[] destJava, int destOffset, int destLength)
+            throws IOException, CertificateException {
+        return NativeCrypto.ENGINE_SSL_read_heap(
+                ssl, destJava, destOffset, destLength, handshakeCallbacks);
+    }
+
+    int writeDirectByteBuffer(long sourceAddress, int sourceLength) throws IOException {
+        return NativeCrypto.ENGINE_SSL_write_direct(
+                ssl, sourceAddress, sourceLength, handshakeCallbacks);
+    }
+
+    int writeArray(byte[] sourceJava, int sourceOffset, int sourceLength) throws IOException {
+        return NativeCrypto.ENGINE_SSL_write_heap(
+                ssl, sourceJava, sourceOffset, sourceLength, handshakeCallbacks);
+    }
+
+    int getPendingReadableBytes() {
+        return NativeCrypto.SSL_pending_readable_bytes(ssl);
+    }
+
+    int getMaxSealOverhead() {
+        return NativeCrypto.SSL_max_seal_overhead(ssl);
+    }
+
+    void close() {
+        NativeCrypto.SSL_free(ssl);
+        ssl = 0L;
+    }
+
+    boolean isClosed() {
+        return ssl == 0L;
+    }
+
+    int getError(int result) {
+        return NativeCrypto.SSL_get_error(ssl, result);
+    }
+
+    byte[] getAlpnSelectedProtocol() {
+        return NativeCrypto.SSL_get0_alpn_selected(ssl);
+    }
+
+    private boolean isClient() {
+        return parameters.getUseClientMode();
+    }
+
+    /**
+     * A utility wrapper that abstracts operations on the underlying native BIO instance.
+     */
+    final class BioWrapper {
+        private long bio;
+
+        private BioWrapper() throws SSLException {
+            this.bio = NativeCrypto.SSL_BIO_new(ssl);
+        }
+
+        int getPendingWrittenBytes() {
+            return NativeCrypto.SSL_pending_written_bytes_in_BIO(bio);
+        }
+
+        int writeDirectByteBuffer(long address, int length) throws IOException {
+            return NativeCrypto.ENGINE_SSL_write_BIO_direct(
+                    ssl, bio, address, length, handshakeCallbacks);
+        }
+
+        int writeArray(byte[] sourceJava, int sourceOffset, int sourceLength) throws IOException {
+            return NativeCrypto.ENGINE_SSL_write_BIO_heap(
+                    ssl, bio, sourceJava, sourceOffset, sourceLength, handshakeCallbacks);
+        }
+
+        int readDirectByteBuffer(long destAddress, int destLength) throws IOException {
+            return NativeCrypto.ENGINE_SSL_read_BIO_direct(
+                    ssl, bio, destAddress, destLength, handshakeCallbacks);
+        }
+
+        int readArray(byte[] destJava, int destOffset, int destLength) throws IOException {
+            return NativeCrypto.ENGINE_SSL_read_BIO_heap(
+                    ssl, bio, destJava, destOffset, destLength, handshakeCallbacks);
+        }
+
+        void close() {
+            NativeCrypto.BIO_free_all(bio);
+            bio = 0L;
+        }
+    }
+}
diff --git a/openjdk-integ-tests/src/test/java/libcore/javax/net/ssl/SSLSessionContextTest.java b/openjdk-integ-tests/src/test/java/libcore/javax/net/ssl/SSLSessionContextTest.java
index 72de22f..91f00ba 100644
--- a/openjdk-integ-tests/src/test/java/libcore/javax/net/ssl/SSLSessionContextTest.java
+++ b/openjdk-integ-tests/src/test/java/libcore/javax/net/ssl/SSLSessionContextTest.java
@@ -39,14 +39,13 @@
 
 @RunWith(JUnit4.class)
 public class SSLSessionContextTest extends AbstractSSLTest {
-
     @Test
     public void test_SSLSessionContext_getIds() {
         TestSSLContext c = TestSSLContext.create();
         assertSSLSessionContextSize(0, c);
         c.close();
 
-        TestSSLSocketPair s = TestSSLSocketPair.create();
+        TestSSLSocketPair s = TestSSLSocketPair.create().connect();
         assertSSLSessionContextSize(1, s.c);
         Enumeration<byte[]> clientIds = s.c.clientContext.getClientSessionContext().getIds();
         Enumeration<byte[]> serverIds = s.c.serverContext.getServerSessionContext().getIds();
@@ -83,7 +82,7 @@
         assertNull(c.serverContext.getServerSessionContext().getSession(new byte[1]));
         c.close();
 
-        TestSSLSocketPair s = TestSSLSocketPair.create();
+        TestSSLSocketPair s = TestSSLSocketPair.create().connect();
         SSLSessionContext client = s.c.clientContext.getClientSessionContext();
         SSLSessionContext server = s.c.serverContext.getServerSessionContext();
         byte[] clientId = client.getIds().nextElement();
@@ -110,7 +109,7 @@
                 c.serverContext.getServerSessionContext().getSessionCacheSize());
         c.close();
 
-        TestSSLSocketPair s = TestSSLSocketPair.create();
+        TestSSLSocketPair s = TestSSLSocketPair.create().connect();
         assertEquals(expectedClientSessionCacheSize,
                 s.c.clientContext.getClientSessionContext().getSessionCacheSize());
         assertEquals(expectedServerSessionCacheSize,
@@ -124,11 +123,9 @@
         int expectedClientSessionCacheSize = expectedClientSslSessionCacheSize(c);
         int expectedServerSessionCacheSize = expectedServerSslSessionCacheSize(c);
         assertNoConnectSetSessionCacheSizeBehavior(
-                expectedClientSessionCacheSize,
-                c.clientContext.getClientSessionContext());
+                expectedClientSessionCacheSize, c.clientContext.getClientSessionContext());
         assertNoConnectSetSessionCacheSizeBehavior(
-                expectedServerSessionCacheSize,
-                c.serverContext.getServerSessionContext());
+                expectedServerSessionCacheSize, c.serverContext.getServerSessionContext());
         c.close();
     }
 
@@ -147,15 +144,13 @@
 
     @Test
     public void test_SSLSessionContext_setSessionCacheSize_oneConnect() {
-        TestSSLSocketPair s = TestSSLSocketPair.create();
+        TestSSLSocketPair s = TestSSLSocketPair.create().connect();
         int expectedClientSessionCacheSize = expectedClientSslSessionCacheSize(s.c);
         int expectedServerSessionCacheSize = expectedServerSslSessionCacheSize(s.c);
         SSLSessionContext client = s.c.clientContext.getClientSessionContext();
         SSLSessionContext server = s.c.serverContext.getServerSessionContext();
-        assertEquals(expectedClientSessionCacheSize,
-                client.getSessionCacheSize());
-        assertEquals(expectedServerSessionCacheSize,
-                server.getSessionCacheSize());
+        assertEquals(expectedClientSessionCacheSize, client.getSessionCacheSize());
+        assertEquals(expectedServerSessionCacheSize, server.getSessionCacheSize());
         assertSSLSessionContextSize(1, s.c);
         s.close();
     }
@@ -212,11 +207,14 @@
         String cipherSuite3 = uniqueCipherSuites.get(2);
 
         List<SSLSocket[]> toClose = new ArrayList<>();
-        toClose.add(TestSSLSocketPair.connect(c, new String[] {cipherSuite1}, null));
+        toClose.add(
+                TestSSLSocketPair.create(c).connect(new String[] {cipherSuite1}, null).sockets());
         assertSSLSessionContextSize(1, c);
-        toClose.add(TestSSLSocketPair.connect(c, new String[] {cipherSuite2}, null));
+        toClose.add(
+                TestSSLSocketPair.create(c).connect(new String[] {cipherSuite2}, null).sockets());
         assertSSLSessionContextSize(2, c);
-        toClose.add(TestSSLSocketPair.connect(c, new String[] {cipherSuite3}, null));
+        toClose.add(
+                TestSSLSocketPair.create(c).connect(new String[] {cipherSuite3}, null).sockets());
         assertSSLSessionContextSize(3, c);
 
         client.setSessionCacheSize(1);
@@ -224,14 +222,17 @@
         assertEquals(1, client.getSessionCacheSize());
         assertEquals(1, server.getSessionCacheSize());
         assertSSLSessionContextSize(1, c);
-        toClose.add(TestSSLSocketPair.connect(c, new String[] {cipherSuite1}, null));
+        toClose.add(
+                TestSSLSocketPair.create(c).connect(new String[] {cipherSuite1}, null).sockets());
         assertSSLSessionContextSize(1, c);
 
         client.setSessionCacheSize(2);
         server.setSessionCacheSize(2);
-        toClose.add(TestSSLSocketPair.connect(c, new String[] {cipherSuite2}, null));
+        toClose.add(
+                TestSSLSocketPair.create(c).connect(new String[] {cipherSuite2}, null).sockets());
         assertSSLSessionContextSize(2, c);
-        toClose.add(TestSSLSocketPair.connect(c, new String[] {cipherSuite3}, null));
+        toClose.add(
+                TestSSLSocketPair.create(c).connect(new String[] {cipherSuite3}, null).sockets());
         assertSSLSessionContextSize(2, c);
 
         for (SSLSocket[] pair : toClose) {
@@ -252,7 +253,7 @@
                 c.serverContext.getServerSessionContext().getSessionTimeout());
         c.close();
 
-        TestSSLSocketPair s = TestSSLSocketPair.create();
+        TestSSLSocketPair s = TestSSLSocketPair.create().connect();
         assertEquals(expectedCacheTimeout,
                 s.c.clientContext.getClientSessionContext().getSessionTimeout());
         assertEquals(expectedCacheTimeout,
@@ -287,7 +288,7 @@
         }
         c.close();
 
-        TestSSLSocketPair s = TestSSLSocketPair.create();
+        TestSSLSocketPair s = TestSSLSocketPair.create().connect();
         assertSSLSessionContextSize(1, s.c);
         Thread.sleep(1000);
         s.c.clientContext.getClientSessionContext().setSessionTimeout(1);
@@ -311,11 +312,10 @@
 
     private static void assertSSLSessionContextSize(
             int expected, SSLSessionContext s, boolean server) {
-        int size = Collections.list(s.getIds()).size();
         if (server && TestSSLContext.sslServerSocketSupportsSessionTickets()) {
-            assertEquals(0, size);
+            assertEquals(0, numSessions(s));
         } else {
-            assertEquals(expected, size);
+            assertEquals(expected, numSessions(s));
         }
     }
 
@@ -331,6 +331,10 @@
         return (isConscrypt(c.serverContext.getProvider())) ? 8 * 3600 : 24 * 3600;
     }
 
+    private static int numSessions(SSLSessionContext s) {
+        return Collections.list(s.getIds()).size();
+    }
+
     private boolean isConscrypt(Provider provider) {
         return "AndroidOpenSSL".equals(provider.getName());
     }
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 53f3a12..928a50a 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
@@ -234,10 +234,10 @@
                 }
                 String[] clientCipherSuiteArray =
                         new String[] {cipherSuite, StandardNames.CIPHER_SUITE_SECURE_RENEGOTIATION};
-                SSLSocket[] pair = TestSSLSocketPair.connect(
-                        c, clientCipherSuiteArray, clientCipherSuiteArray);
-                SSLSocket server = pair[0];
-                SSLSocket client = pair[1];
+                TestSSLSocketPair socketPair = TestSSLSocketPair.create(c).connect(
+                        clientCipherSuiteArray, clientCipherSuiteArray);
+                SSLSocket server = socketPair.server;
+                SSLSocket client = socketPair.client;
                 // Check that the client can read the message sent by the server
                 server.getOutputStream().write(serverToClient);
                 byte[] clientFromServer = new byte[serverToClient.length];
@@ -603,7 +603,6 @@
                 byte[] id = session.getId();
                 assertNotNull(id);
                 assertEquals(32, id.length);
-                assertNotNull(c.clientContext.getClientSessionContext().getSession(id));
                 assertNotNull(cipherSuite);
                 assertTrue(Arrays.asList(client.getEnabledCipherSuites()).contains(cipherSuite));
                 assertTrue(Arrays.asList(c.serverSocket.getEnabledCipherSuites())
@@ -762,7 +761,7 @@
     @Test
     public void test_SSLSocket_setUseClientMode_afterHandshake() throws Exception {
         // can't set after handshake
-        TestSSLSocketPair pair = TestSSLSocketPair.create();
+        TestSSLSocketPair pair = TestSSLSocketPair.create().connect();
         try {
             pair.server.setUseClientMode(false);
             fail();
@@ -1365,7 +1364,7 @@
 
     @Test
     public void test_SSLSocket_close() throws Exception {
-        TestSSLSocketPair pair = TestSSLSocketPair.create();
+        TestSSLSocketPair pair = TestSSLSocketPair.create().connect();
         SSLSocket server = pair.server;
         SSLSocket client = pair.client;
         assertFalse(server.isClosed());
@@ -1778,7 +1777,7 @@
 
     @Test
     public void test_TestSSLSocketPair_create() {
-        TestSSLSocketPair test = TestSSLSocketPair.create();
+        TestSSLSocketPair test = TestSSLSocketPair.create().connect();
         assertNotNull(test.c);
         assertNotNull(test.server);
         assertNotNull(test.client);
diff --git a/openjdk/src/main/java/org/conscrypt/Platform.java b/openjdk/src/main/java/org/conscrypt/Platform.java
index 7b5adb6..4bba5c8 100644
--- a/openjdk/src/main/java/org/conscrypt/Platform.java
+++ b/openjdk/src/main/java/org/conscrypt/Platform.java
@@ -362,15 +362,16 @@
      * Pre-Java-8 backward compatibility.
      */
 
-    static SSLSession wrapSSLSession(AbstractOpenSSLSession sslSession) {
-        return new OpenSSLExtendedSessionImpl(sslSession);
+    static SSLSession wrapSSLSession(ActiveSession sslSession) {
+        return new DelegatingExtendedSSLSession(sslSession);
     }
 
     @SuppressWarnings("unused")
     static SSLSession unwrapSSLSession(SSLSession sslSession) {
-        if (sslSession instanceof OpenSSLExtendedSessionImpl) {
-            return ((OpenSSLExtendedSessionImpl) sslSession).getDelegate();
+        if (sslSession instanceof DelegatingExtendedSSLSession) {
+            return ((DelegatingExtendedSSLSession) sslSession).getDelegate();
         }
+
         return sslSession;
     }
 
diff --git a/openjdk/src/test/java/org/conscrypt/AbstractSessionContextTest.java b/openjdk/src/test/java/org/conscrypt/AbstractSessionContextTest.java
index 756e294..1cda6f9 100644
--- a/openjdk/src/test/java/org/conscrypt/AbstractSessionContextTest.java
+++ b/openjdk/src/test/java/org/conscrypt/AbstractSessionContextTest.java
@@ -1,7 +1,7 @@
 /*
- * Copyright 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License", "www.google.com", 443);
+ * 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
  *
@@ -17,649 +17,118 @@
 package org.conscrypt;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
+import java.security.cert.Certificate;
 import javax.net.ssl.SSLSession;
-import org.junit.After;
-import org.junit.BeforeClass;
+import org.junit.Before;
 import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
 
-@RunWith(JUnit4.class)
-public class AbstractSessionContextTest {
-    /*
-     * Taken from external/boringssl/src/ssl/ssl_test.cc: kOpenSSLSession is a
-     * serialized SSL_SESSION.
-     */
-    private static final byte[] kOpenSSLSession = new byte[] {(byte) 0x30, (byte) 0x82, (byte) 0x05,
-            (byte) 0xAA, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x02, (byte) 0x02,
-            (byte) 0x03, (byte) 0x03, (byte) 0x04, (byte) 0x02, (byte) 0xC0, (byte) 0x2F,
-            (byte) 0x04, (byte) 0x20, (byte) 0x06, (byte) 0xE5, (byte) 0x0D, (byte) 0x67,
-            (byte) 0x76, (byte) 0xAE, (byte) 0x18, (byte) 0x7E, (byte) 0x66, (byte) 0xDE,
-            (byte) 0xA3, (byte) 0x5C, (byte) 0xF0, (byte) 0x2E, (byte) 0x43, (byte) 0x51,
-            (byte) 0x2A, (byte) 0x60, (byte) 0x97, (byte) 0x19, (byte) 0xD3, (byte) 0x60,
-            (byte) 0x5A, (byte) 0xF1, (byte) 0x93, (byte) 0xDD, (byte) 0xCB, (byte) 0x24,
-            (byte) 0x57, (byte) 0x4C, (byte) 0x90, (byte) 0x90, (byte) 0x04, (byte) 0x30,
-            (byte) 0x26, (byte) 0x5A, (byte) 0xE5, (byte) 0xCE, (byte) 0x40, (byte) 0x16,
-            (byte) 0x04, (byte) 0xE5, (byte) 0xA2, (byte) 0x2E, (byte) 0x3F, (byte) 0xE3,
-            (byte) 0x27, (byte) 0xBE, (byte) 0x83, (byte) 0xEE, (byte) 0x5F, (byte) 0x94,
-            (byte) 0x5E, (byte) 0x88, (byte) 0xB3, (byte) 0x3F, (byte) 0x62, (byte) 0x88,
-            (byte) 0xD8, (byte) 0x2E, (byte) 0xC8, (byte) 0xD8, (byte) 0x57, (byte) 0x1C,
-            (byte) 0xA8, (byte) 0xC9, (byte) 0x88, (byte) 0x7C, (byte) 0x59, (byte) 0xA6,
-            (byte) 0x91, (byte) 0x4C, (byte) 0xB7, (byte) 0xDA, (byte) 0x72, (byte) 0x09,
-            (byte) 0xD2, (byte) 0x66, (byte) 0x47, (byte) 0x21, (byte) 0x6A, (byte) 0x09,
-            (byte) 0xA1, (byte) 0x06, (byte) 0x02, (byte) 0x04, (byte) 0x54, (byte) 0x43,
-            (byte) 0x3B, (byte) 0x8E, (byte) 0xA2, (byte) 0x04, (byte) 0x02, (byte) 0x02,
-            (byte) 0x01, (byte) 0x2C, (byte) 0xA3, (byte) 0x82, (byte) 0x04, (byte) 0x7A,
-            (byte) 0x30, (byte) 0x82, (byte) 0x04, (byte) 0x76, (byte) 0x30, (byte) 0x82,
-            (byte) 0x03, (byte) 0x5E, (byte) 0xA0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
-            (byte) 0x02, (byte) 0x02, (byte) 0x08, (byte) 0x2B, (byte) 0xD7, (byte) 0x54,
-            (byte) 0xBE, (byte) 0xC3, (byte) 0xD6, (byte) 0x4A, (byte) 0x55, (byte) 0x30,
-            (byte) 0x0D, (byte) 0x06, (byte) 0x09, (byte) 0x2A, (byte) 0x86, (byte) 0x48,
-            (byte) 0x86, (byte) 0xF7, (byte) 0x0D, (byte) 0x01, (byte) 0x01, (byte) 0x05,
-            (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x49, (byte) 0x31, (byte) 0x0B,
-            (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
-            (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31,
-            (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x04, (byte) 0x0A, (byte) 0x13, (byte) 0x0A, (byte) 0x47, (byte) 0x6F,
-            (byte) 0x6F, (byte) 0x67, (byte) 0x6C, (byte) 0x65, (byte) 0x20, (byte) 0x49,
-            (byte) 0x6E, (byte) 0x63, (byte) 0x31, (byte) 0x25, (byte) 0x30, (byte) 0x23,
-            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x13,
-            (byte) 0x1C, (byte) 0x47, (byte) 0x6F, (byte) 0x6F, (byte) 0x67, (byte) 0x6C,
-            (byte) 0x65, (byte) 0x20, (byte) 0x49, (byte) 0x6E, (byte) 0x74, (byte) 0x65,
-            (byte) 0x72, (byte) 0x6E, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x41,
-            (byte) 0x75, (byte) 0x74, (byte) 0x68, (byte) 0x6F, (byte) 0x72, (byte) 0x69,
-            (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x47, (byte) 0x32, (byte) 0x30,
-            (byte) 0x1E, (byte) 0x17, (byte) 0x0D, (byte) 0x31, (byte) 0x34, (byte) 0x31,
-            (byte) 0x30, (byte) 0x30, (byte) 0x38, (byte) 0x31, (byte) 0x32, (byte) 0x30,
-            (byte) 0x37, (byte) 0x35, (byte) 0x37, (byte) 0x5A, (byte) 0x17, (byte) 0x0D,
-            (byte) 0x31, (byte) 0x35, (byte) 0x30, (byte) 0x31, (byte) 0x30, (byte) 0x36,
-            (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30,
-            (byte) 0x5A, (byte) 0x30, (byte) 0x68, (byte) 0x31, (byte) 0x0B, (byte) 0x30,
-            (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
-            (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x13,
-            (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
-            (byte) 0x08, (byte) 0x0C, (byte) 0x0A, (byte) 0x43, (byte) 0x61, (byte) 0x6C,
-            (byte) 0x69, (byte) 0x66, (byte) 0x6F, (byte) 0x72, (byte) 0x6E, (byte) 0x69,
-            (byte) 0x61, (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06,
-            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x0C, (byte) 0x0D,
-            (byte) 0x4D, (byte) 0x6F, (byte) 0x75, (byte) 0x6E, (byte) 0x74, (byte) 0x61,
-            (byte) 0x69, (byte) 0x6E, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65,
-            (byte) 0x77, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06,
-            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0A, (byte) 0x0C, (byte) 0x0A,
-            (byte) 0x47, (byte) 0x6F, (byte) 0x6F, (byte) 0x67, (byte) 0x6C, (byte) 0x65,
-            (byte) 0x20, (byte) 0x49, (byte) 0x6E, (byte) 0x63, (byte) 0x31, (byte) 0x17,
-            (byte) 0x30, (byte) 0x15, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
-            (byte) 0x03, (byte) 0x0C, (byte) 0x0E, (byte) 0x77, (byte) 0x77, (byte) 0x77,
-            (byte) 0x2E, (byte) 0x67, (byte) 0x6F, (byte) 0x6F, (byte) 0x67, (byte) 0x6C,
-            (byte) 0x65, (byte) 0x2E, (byte) 0x63, (byte) 0x6F, (byte) 0x6D, (byte) 0x30,
-            (byte) 0x82, (byte) 0x01, (byte) 0x22, (byte) 0x30, (byte) 0x0D, (byte) 0x06,
-            (byte) 0x09, (byte) 0x2A, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xF7,
-            (byte) 0x0D, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00,
-            (byte) 0x03, (byte) 0x82, (byte) 0x01, (byte) 0x0F, (byte) 0x00, (byte) 0x30,
-            (byte) 0x82, (byte) 0x01, (byte) 0x0A, (byte) 0x02, (byte) 0x82, (byte) 0x01,
-            (byte) 0x01, (byte) 0x00, (byte) 0x9C, (byte) 0x29, (byte) 0xE2, (byte) 0xEB,
-            (byte) 0xA6, (byte) 0x50, (byte) 0x02, (byte) 0xF8, (byte) 0xBA, (byte) 0x1F,
-            (byte) 0xCB, (byte) 0xCB, (byte) 0x7F, (byte) 0xC0, (byte) 0x3C, (byte) 0x2D,
-            (byte) 0x07, (byte) 0xA7, (byte) 0xAE, (byte) 0xEF, (byte) 0x60, (byte) 0x95,
-            (byte) 0xA7, (byte) 0x47, (byte) 0x09, (byte) 0xE1, (byte) 0x5D, (byte) 0xE5,
-            (byte) 0x92, (byte) 0x73, (byte) 0x7A, (byte) 0x86, (byte) 0xE1, (byte) 0xFD,
-            (byte) 0x72, (byte) 0xDE, (byte) 0x85, (byte) 0x16, (byte) 0x4E, (byte) 0xF4,
-            (byte) 0xA1, (byte) 0x12, (byte) 0x21, (byte) 0xFD, (byte) 0x50, (byte) 0x4D,
-            (byte) 0x04, (byte) 0x1C, (byte) 0xFD, (byte) 0xD3, (byte) 0x48, (byte) 0xD8,
-            (byte) 0xCB, (byte) 0xEE, (byte) 0xF5, (byte) 0xD7, (byte) 0x52, (byte) 0x66,
-            (byte) 0xD5, (byte) 0xBF, (byte) 0x22, (byte) 0xA8, (byte) 0xE4, (byte) 0xD0,
-            (byte) 0xF5, (byte) 0xA4, (byte) 0xF9, (byte) 0x0B, (byte) 0xB4, (byte) 0x84,
-            (byte) 0x84, (byte) 0xD7, (byte) 0x10, (byte) 0x14, (byte) 0x9B, (byte) 0xEA,
-            (byte) 0xCC, (byte) 0x7D, (byte) 0xDE, (byte) 0x30, (byte) 0xF9, (byte) 0x1B,
-            (byte) 0xE9, (byte) 0x94, (byte) 0x96, (byte) 0x1A, (byte) 0x6D, (byte) 0x72,
-            (byte) 0x18, (byte) 0x5E, (byte) 0xCC, (byte) 0x09, (byte) 0x04, (byte) 0xC6,
-            (byte) 0x41, (byte) 0x71, (byte) 0x76, (byte) 0xD1, (byte) 0x29, (byte) 0x3F,
-            (byte) 0x3B, (byte) 0x5E, (byte) 0x85, (byte) 0x4A, (byte) 0x30, (byte) 0x32,
-            (byte) 0x9D, (byte) 0x4F, (byte) 0xDB, (byte) 0xDE, (byte) 0x82, (byte) 0x66,
-            (byte) 0x39, (byte) 0xCB, (byte) 0x5C, (byte) 0xC9, (byte) 0xC5, (byte) 0x98,
-            (byte) 0x91, (byte) 0x8D, (byte) 0x32, (byte) 0xB5, (byte) 0x2F, (byte) 0xE4,
-            (byte) 0xDC, (byte) 0xB0, (byte) 0x6E, (byte) 0x21, (byte) 0xDE, (byte) 0x39,
-            (byte) 0x3C, (byte) 0x96, (byte) 0xA8, (byte) 0x32, (byte) 0xA8, (byte) 0xC1,
-            (byte) 0xD1, (byte) 0x6C, (byte) 0xA9, (byte) 0xAA, (byte) 0xF3, (byte) 0x5E,
-            (byte) 0x24, (byte) 0x70, (byte) 0xB7, (byte) 0xAB, (byte) 0x92, (byte) 0x63,
-            (byte) 0x08, (byte) 0x1E, (byte) 0x11, (byte) 0x3F, (byte) 0xB3, (byte) 0x5F,
-            (byte) 0xC7, (byte) 0x98, (byte) 0xE3, (byte) 0x1D, (byte) 0x2A, (byte) 0xC2,
-            (byte) 0x32, (byte) 0x1C, (byte) 0x3C, (byte) 0x95, (byte) 0x43, (byte) 0x16,
-            (byte) 0xE0, (byte) 0x46, (byte) 0x83, (byte) 0xC6, (byte) 0x36, (byte) 0x91,
-            (byte) 0xF4, (byte) 0xA0, (byte) 0xE1, (byte) 0x3C, (byte) 0xB8, (byte) 0x23,
-            (byte) 0xB2, (byte) 0x4F, (byte) 0x8B, (byte) 0x0C, (byte) 0x8C, (byte) 0x92,
-            (byte) 0x45, (byte) 0x24, (byte) 0x43, (byte) 0x68, (byte) 0x24, (byte) 0x06,
-            (byte) 0x84, (byte) 0x43, (byte) 0x96, (byte) 0x2C, (byte) 0x96, (byte) 0x55,
-            (byte) 0x2F, (byte) 0x32, (byte) 0xE8, (byte) 0xE0, (byte) 0xDE, (byte) 0xBF,
-            (byte) 0x52, (byte) 0x57, (byte) 0x2D, (byte) 0x08, (byte) 0x71, (byte) 0x25,
-            (byte) 0x96, (byte) 0x90, (byte) 0x54, (byte) 0x4A, (byte) 0xF1, (byte) 0x0E,
-            (byte) 0xC8, (byte) 0x58, (byte) 0x1A, (byte) 0xE7, (byte) 0x6A, (byte) 0xAB,
-            (byte) 0xA0, (byte) 0x68, (byte) 0xE0, (byte) 0xAD, (byte) 0xFD, (byte) 0xD6,
-            (byte) 0x39, (byte) 0x0F, (byte) 0x76, (byte) 0xE4, (byte) 0xC1, (byte) 0x70,
-            (byte) 0xCD, (byte) 0xDE, (byte) 0x80, (byte) 0x2B, (byte) 0xE2, (byte) 0x1C,
-            (byte) 0x87, (byte) 0x48, (byte) 0x03, (byte) 0x46, (byte) 0x0F, (byte) 0x2C,
-            (byte) 0x41, (byte) 0xF7, (byte) 0x4B, (byte) 0x1F, (byte) 0x93, (byte) 0xAE,
-            (byte) 0x3F, (byte) 0x57, (byte) 0x1F, (byte) 0x2D, (byte) 0xF5, (byte) 0x35,
-            (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xA3,
-            (byte) 0x82, (byte) 0x01, (byte) 0x41, (byte) 0x30, (byte) 0x82, (byte) 0x01,
-            (byte) 0x3D, (byte) 0x30, (byte) 0x1D, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x1D, (byte) 0x25, (byte) 0x04, (byte) 0x16, (byte) 0x30, (byte) 0x14,
-            (byte) 0x06, (byte) 0x08, (byte) 0x2B, (byte) 0x06, (byte) 0x01, (byte) 0x05,
-            (byte) 0x05, (byte) 0x07, (byte) 0x03, (byte) 0x01, (byte) 0x06, (byte) 0x08,
-            (byte) 0x2B, (byte) 0x06, (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07,
-            (byte) 0x03, (byte) 0x02, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03,
-            (byte) 0x55, (byte) 0x1D, (byte) 0x11, (byte) 0x04, (byte) 0x12, (byte) 0x30,
-            (byte) 0x10, (byte) 0x82, (byte) 0x0E, (byte) 0x77, (byte) 0x77, (byte) 0x77,
-            (byte) 0x2E, (byte) 0x67, (byte) 0x6F, (byte) 0x6F, (byte) 0x67, (byte) 0x6C,
-            (byte) 0x65, (byte) 0x2E, (byte) 0x63, (byte) 0x6F, (byte) 0x6D, (byte) 0x30,
-            (byte) 0x68, (byte) 0x06, (byte) 0x08, (byte) 0x2B, (byte) 0x06, (byte) 0x01,
-            (byte) 0x05, (byte) 0x05, (byte) 0x07, (byte) 0x01, (byte) 0x01, (byte) 0x04,
-            (byte) 0x5C, (byte) 0x30, (byte) 0x5A, (byte) 0x30, (byte) 0x2B, (byte) 0x06,
-            (byte) 0x08, (byte) 0x2B, (byte) 0x06, (byte) 0x01, (byte) 0x05, (byte) 0x05,
-            (byte) 0x07, (byte) 0x30, (byte) 0x02, (byte) 0x86, (byte) 0x1F, (byte) 0x68,
-            (byte) 0x74, (byte) 0x74, (byte) 0x70, (byte) 0x3A, (byte) 0x2F, (byte) 0x2F,
-            (byte) 0x70, (byte) 0x6B, (byte) 0x69, (byte) 0x2E, (byte) 0x67, (byte) 0x6F,
-            (byte) 0x6F, (byte) 0x67, (byte) 0x6C, (byte) 0x65, (byte) 0x2E, (byte) 0x63,
-            (byte) 0x6F, (byte) 0x6D, (byte) 0x2F, (byte) 0x47, (byte) 0x49, (byte) 0x41,
-            (byte) 0x47, (byte) 0x32, (byte) 0x2E, (byte) 0x63, (byte) 0x72, (byte) 0x74,
-            (byte) 0x30, (byte) 0x2B, (byte) 0x06, (byte) 0x08, (byte) 0x2B, (byte) 0x06,
-            (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, (byte) 0x30, (byte) 0x01,
-            (byte) 0x86, (byte) 0x1F, (byte) 0x68, (byte) 0x74, (byte) 0x74, (byte) 0x70,
-            (byte) 0x3A, (byte) 0x2F, (byte) 0x2F, (byte) 0x63, (byte) 0x6C, (byte) 0x69,
-            (byte) 0x65, (byte) 0x6E, (byte) 0x74, (byte) 0x73, (byte) 0x31, (byte) 0x2E,
-            (byte) 0x67, (byte) 0x6F, (byte) 0x6F, (byte) 0x67, (byte) 0x6C, (byte) 0x65,
-            (byte) 0x2E, (byte) 0x63, (byte) 0x6F, (byte) 0x6D, (byte) 0x2F, (byte) 0x6F,
-            (byte) 0x63, (byte) 0x73, (byte) 0x70, (byte) 0x30, (byte) 0x1D, (byte) 0x06,
-            (byte) 0x03, (byte) 0x55, (byte) 0x1D, (byte) 0x0E, (byte) 0x04, (byte) 0x16,
-            (byte) 0x04, (byte) 0x14, (byte) 0x3B, (byte) 0x6B, (byte) 0xE0, (byte) 0x9C,
-            (byte) 0xC6, (byte) 0xC6, (byte) 0x41, (byte) 0xC8, (byte) 0xEA, (byte) 0x5C,
-            (byte) 0xFB, (byte) 0x1A, (byte) 0x58, (byte) 0x15, (byte) 0xC2, (byte) 0x1B,
-            (byte) 0x9D, (byte) 0x43, (byte) 0x19, (byte) 0x85, (byte) 0x30, (byte) 0x0C,
-            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1D, (byte) 0x13, (byte) 0x01,
-            (byte) 0x01, (byte) 0xFF, (byte) 0x04, (byte) 0x02, (byte) 0x30, (byte) 0x00,
-            (byte) 0x30, (byte) 0x1F, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1D,
-            (byte) 0x23, (byte) 0x04, (byte) 0x18, (byte) 0x30, (byte) 0x16, (byte) 0x80,
-            (byte) 0x14, (byte) 0x4A, (byte) 0xDD, (byte) 0x06, (byte) 0x16, (byte) 0x1B,
-            (byte) 0xBC, (byte) 0xF6, (byte) 0x68, (byte) 0xB5, (byte) 0x76, (byte) 0xF5,
-            (byte) 0x81, (byte) 0xB6, (byte) 0xBB, (byte) 0x62, (byte) 0x1A, (byte) 0xBA,
-            (byte) 0x5A, (byte) 0x81, (byte) 0x2F, (byte) 0x30, (byte) 0x17, (byte) 0x06,
-            (byte) 0x03, (byte) 0x55, (byte) 0x1D, (byte) 0x20, (byte) 0x04, (byte) 0x10,
-            (byte) 0x30, (byte) 0x0E, (byte) 0x30, (byte) 0x0C, (byte) 0x06, (byte) 0x0A,
-            (byte) 0x2B, (byte) 0x06, (byte) 0x01, (byte) 0x04, (byte) 0x01, (byte) 0xD6,
-            (byte) 0x79, (byte) 0x02, (byte) 0x05, (byte) 0x01, (byte) 0x30, (byte) 0x30,
-            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1D, (byte) 0x1F, (byte) 0x04,
-            (byte) 0x29, (byte) 0x30, (byte) 0x27, (byte) 0x30, (byte) 0x25, (byte) 0xA0,
-            (byte) 0x23, (byte) 0xA0, (byte) 0x21, (byte) 0x86, (byte) 0x1F, (byte) 0x68,
-            (byte) 0x74, (byte) 0x74, (byte) 0x70, (byte) 0x3A, (byte) 0x2F, (byte) 0x2F,
-            (byte) 0x70, (byte) 0x6B, (byte) 0x69, (byte) 0x2E, (byte) 0x67, (byte) 0x6F,
-            (byte) 0x6F, (byte) 0x67, (byte) 0x6C, (byte) 0x65, (byte) 0x2E, (byte) 0x63,
-            (byte) 0x6F, (byte) 0x6D, (byte) 0x2F, (byte) 0x47, (byte) 0x49, (byte) 0x41,
-            (byte) 0x47, (byte) 0x32, (byte) 0x2E, (byte) 0x63, (byte) 0x72, (byte) 0x6C,
-            (byte) 0x30, (byte) 0x0D, (byte) 0x06, (byte) 0x09, (byte) 0x2A, (byte) 0x86,
-            (byte) 0x48, (byte) 0x86, (byte) 0xF7, (byte) 0x0D, (byte) 0x01, (byte) 0x01,
-            (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x82, (byte) 0x01,
-            (byte) 0x01, (byte) 0x00, (byte) 0x9A, (byte) 0x39, (byte) 0x70, (byte) 0x81,
-            (byte) 0x76, (byte) 0x8A, (byte) 0x94, (byte) 0xCB, (byte) 0x96, (byte) 0xF1,
-            (byte) 0xCA, (byte) 0xAF, (byte) 0x96, (byte) 0xAE, (byte) 0x1D, (byte) 0x73,
-            (byte) 0xB3, (byte) 0x2C, (byte) 0x82, (byte) 0x16, (byte) 0x29, (byte) 0xB5,
-            (byte) 0x3C, (byte) 0x7E, (byte) 0x55, (byte) 0x53, (byte) 0x6F, (byte) 0xB2,
-            (byte) 0xBC, (byte) 0x34, (byte) 0x96, (byte) 0xAE, (byte) 0x00, (byte) 0xD8,
-            (byte) 0xF2, (byte) 0x26, (byte) 0xD1, (byte) 0x18, (byte) 0x99, (byte) 0x9F,
-            (byte) 0x7D, (byte) 0xFD, (byte) 0xEB, (byte) 0xE0, (byte) 0xBB, (byte) 0x9D,
-            (byte) 0xE6, (byte) 0x46, (byte) 0xA5, (byte) 0x74, (byte) 0xAB, (byte) 0x3D,
-            (byte) 0x93, (byte) 0xC6, (byte) 0x25, (byte) 0x28, (byte) 0x3D, (byte) 0xC8,
-            (byte) 0x4C, (byte) 0x6E, (byte) 0xCF, (byte) 0xD1, (byte) 0x84, (byte) 0xFF,
-            (byte) 0x46, (byte) 0x4F, (byte) 0x21, (byte) 0x2E, (byte) 0x07, (byte) 0xC4,
-            (byte) 0xB8, (byte) 0xB7, (byte) 0x2A, (byte) 0xE5, (byte) 0xC7, (byte) 0x34,
-            (byte) 0xC6, (byte) 0xA9, (byte) 0x84, (byte) 0xE3, (byte) 0x6C, (byte) 0x49,
-            (byte) 0xF8, (byte) 0x4A, (byte) 0x36, (byte) 0xBB, (byte) 0x3A, (byte) 0xBD,
-            (byte) 0xAD, (byte) 0x8A, (byte) 0x2B, (byte) 0x73, (byte) 0x97, (byte) 0xA6,
-            (byte) 0x30, (byte) 0x2C, (byte) 0x5F, (byte) 0xE4, (byte) 0xBD, (byte) 0x13,
-            (byte) 0x24, (byte) 0xE5, (byte) 0xD9, (byte) 0xA8, (byte) 0x74, (byte) 0x29,
-            (byte) 0x38, (byte) 0x47, (byte) 0x2E, (byte) 0xA6, (byte) 0xD6, (byte) 0x50,
-            (byte) 0xE0, (byte) 0xE8, (byte) 0xDD, (byte) 0x60, (byte) 0xC7, (byte) 0xD2,
-            (byte) 0xC6, (byte) 0x4E, (byte) 0x54, (byte) 0xCE, (byte) 0xE7, (byte) 0x94,
-            (byte) 0x84, (byte) 0x0D, (byte) 0xE8, (byte) 0x81, (byte) 0x92, (byte) 0x91,
-            (byte) 0x71, (byte) 0x19, (byte) 0x1D, (byte) 0x07, (byte) 0x75, (byte) 0x9E,
-            (byte) 0x59, (byte) 0x1A, (byte) 0x7E, (byte) 0x9D, (byte) 0x84, (byte) 0x61,
-            (byte) 0xC7, (byte) 0x84, (byte) 0xAD, (byte) 0xA3, (byte) 0x6A, (byte) 0xED,
-            (byte) 0xD8, (byte) 0x0D, (byte) 0x0C, (byte) 0x2A, (byte) 0x66, (byte) 0x3D,
-            (byte) 0xD7, (byte) 0xAE, (byte) 0x46, (byte) 0x1D, (byte) 0x4A, (byte) 0x8C,
-            (byte) 0x2B, (byte) 0xD6, (byte) 0x1A, (byte) 0x69, (byte) 0x71, (byte) 0xC3,
-            (byte) 0x5E, (byte) 0xA0, (byte) 0x6E, (byte) 0xED, (byte) 0x27, (byte) 0x9F,
-            (byte) 0xAF, (byte) 0x5B, (byte) 0x92, (byte) 0xA0, (byte) 0x03, (byte) 0xFD,
-            (byte) 0x83, (byte) 0x22, (byte) 0x09, (byte) 0x29, (byte) 0xE8, (byte) 0xA1,
-            (byte) 0x32, (byte) 0x2B, (byte) 0xEC, (byte) 0x1A, (byte) 0xA2, (byte) 0x75,
-            (byte) 0x4C, (byte) 0x3E, (byte) 0x99, (byte) 0x71, (byte) 0xCE, (byte) 0x8B,
-            (byte) 0x31, (byte) 0xEF, (byte) 0x9D, (byte) 0x37, (byte) 0x63, (byte) 0xFC,
-            (byte) 0x71, (byte) 0x91, (byte) 0x10, (byte) 0x1E, (byte) 0xD0, (byte) 0xF5,
-            (byte) 0xCB, (byte) 0x6F, (byte) 0x7A, (byte) 0xBA, (byte) 0x5E, (byte) 0x0C,
-            (byte) 0x8A, (byte) 0xFA, (byte) 0xA4, (byte) 0xDE, (byte) 0x36, (byte) 0xAD,
-            (byte) 0x51, (byte) 0x52, (byte) 0xFC, (byte) 0xFE, (byte) 0x10, (byte) 0xB0,
-            (byte) 0x81, (byte) 0xC8, (byte) 0x7D, (byte) 0x03, (byte) 0xC3, (byte) 0xB8,
-            (byte) 0x3C, (byte) 0x66, (byte) 0x6A, (byte) 0xF5, (byte) 0x6A, (byte) 0x81,
-            (byte) 0x7C, (byte) 0x45, (byte) 0xA6, (byte) 0x23, (byte) 0x21, (byte) 0xE1,
-            (byte) 0xD5, (byte) 0xD3, (byte) 0xED, (byte) 0x6E, (byte) 0x0D, (byte) 0x65,
-            (byte) 0x39, (byte) 0x77, (byte) 0x58, (byte) 0x09, (byte) 0x6B, (byte) 0x63,
-            (byte) 0xA4, (byte) 0x02, (byte) 0x04, (byte) 0x00, (byte) 0xA5, (byte) 0x03,
-            (byte) 0x02, (byte) 0x01, (byte) 0x14, (byte) 0xA9, (byte) 0x05, (byte) 0x02,
-            (byte) 0x03, (byte) 0x01, (byte) 0x89, (byte) 0xC0, (byte) 0xAA, (byte) 0x81,
-            (byte) 0xA7, (byte) 0x04, (byte) 0x81, (byte) 0xA4, (byte) 0x1C, (byte) 0x14,
-            (byte) 0x42, (byte) 0xFA, (byte) 0x1E, (byte) 0x3A, (byte) 0x4D, (byte) 0x0A,
-            (byte) 0x83, (byte) 0x7E, (byte) 0x92, (byte) 0x61, (byte) 0x37, (byte) 0x0B,
-            (byte) 0x12, (byte) 0x45, (byte) 0xEA, (byte) 0x2B, (byte) 0x03, (byte) 0x81,
-            (byte) 0x7C, (byte) 0x5F, (byte) 0x6F, (byte) 0x13, (byte) 0x82, (byte) 0x97,
-            (byte) 0xD0, (byte) 0xDC, (byte) 0x5E, (byte) 0x2F, (byte) 0x08, (byte) 0xDC,
-            (byte) 0x0D, (byte) 0x3A, (byte) 0x6C, (byte) 0xBA, (byte) 0x1D, (byte) 0xEA,
-            (byte) 0x5C, (byte) 0x46, (byte) 0x99, (byte) 0xF7, (byte) 0xDD, (byte) 0xAB,
-            (byte) 0xD4, (byte) 0xDD, (byte) 0xFC, (byte) 0x54, (byte) 0x37, (byte) 0x32,
-            (byte) 0x4B, (byte) 0xA3, (byte) 0xFB, (byte) 0x23, (byte) 0xA1, (byte) 0xC1,
-            (byte) 0x60, (byte) 0xDF, (byte) 0x41, (byte) 0xB0, (byte) 0xD1, (byte) 0xCC,
-            (byte) 0xDF, (byte) 0xAD, (byte) 0xB3, (byte) 0x66, (byte) 0x76, (byte) 0x36,
-            (byte) 0xEC, (byte) 0x6A, (byte) 0x53, (byte) 0xC3, (byte) 0xE2, (byte) 0xB0,
-            (byte) 0x77, (byte) 0xBE, (byte) 0x75, (byte) 0x08, (byte) 0xBA, (byte) 0x17,
-            (byte) 0x14, (byte) 0xFA, (byte) 0x1A, (byte) 0x30, (byte) 0xE7, (byte) 0xB9,
-            (byte) 0xED, (byte) 0xD6, (byte) 0xC1, (byte) 0xA5, (byte) 0x7A, (byte) 0x2B,
-            (byte) 0xA3, (byte) 0xA3, (byte) 0xDD, (byte) 0xDC, (byte) 0x14, (byte) 0xDB,
-            (byte) 0x7F, (byte) 0xF4, (byte) 0xF3, (byte) 0xAF, (byte) 0xCF, (byte) 0x0A,
-            (byte) 0xD3, (byte) 0xAC, (byte) 0x84, (byte) 0x39, (byte) 0x30, (byte) 0xCA,
-            (byte) 0x3C, (byte) 0xD8, (byte) 0xF7, (byte) 0xFA, (byte) 0x29, (byte) 0xDB,
-            (byte) 0x31, (byte) 0xA5, (byte) 0x62, (byte) 0x82, (byte) 0xD2, (byte) 0xB8,
-            (byte) 0x3C, (byte) 0xBC, (byte) 0x8F, (byte) 0xAB, (byte) 0xE4, (byte) 0xE8,
-            (byte) 0xA7, (byte) 0x2C, (byte) 0xEF, (byte) 0xC7, (byte) 0xD5, (byte) 0x12,
-            (byte) 0x16, (byte) 0x04, (byte) 0x6F, (byte) 0xCA, (byte) 0xEA, (byte) 0x31,
-            (byte) 0x9F, (byte) 0x41, (byte) 0xE0, (byte) 0x6F, (byte) 0xE4, (byte) 0x74,
-            (byte) 0x03, (byte) 0x78, (byte) 0xFA, (byte) 0x48, (byte) 0xB4, (byte) 0x6E,
-            (byte) 0xC8, (byte) 0xE7, (byte) 0x40, (byte) 0x8B, (byte) 0x88, (byte) 0x2F,
-            (byte) 0xED, (byte) 0x8E, (byte) 0x68, (byte) 0x96, (byte) 0x2C, (byte) 0xA7,
-            (byte) 0xB6, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0x00};
+public abstract class AbstractSessionContextTest<T extends AbstractSessionContext> {
+    private T context;
 
-    private static final byte[] DUMMY_CERT =
-            new byte[] {(byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x58, (byte) 0x30,
-                    (byte) 0x82, (byte) 0x01, (byte) 0xC1, (byte) 0xA0, (byte) 0x03, (byte) 0x02,
-                    (byte) 0x01, (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xFB,
-                    (byte) 0xB0, (byte) 0x4C, (byte) 0x2E, (byte) 0xAB, (byte) 0x10, (byte) 0x9B,
-                    (byte) 0x0C, (byte) 0x30, (byte) 0x0D, (byte) 0x06, (byte) 0x09, (byte) 0x2A,
-                    (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xF7, (byte) 0x0D, (byte) 0x01,
-                    (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x45,
-                    (byte) 0x31, (byte) 0x0B, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03,
-                    (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41,
-                    (byte) 0x55, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06,
-                    (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0C, (byte) 0x0A,
-                    (byte) 0x53, (byte) 0x6F, (byte) 0x6D, (byte) 0x65, (byte) 0x2D, (byte) 0x53,
-                    (byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21,
-                    (byte) 0x30, (byte) 0x1F, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
-                    (byte) 0x0A, (byte) 0x0C, (byte) 0x18, (byte) 0x49, (byte) 0x6E, (byte) 0x74,
-                    (byte) 0x65, (byte) 0x72, (byte) 0x6E, (byte) 0x65, (byte) 0x74, (byte) 0x20,
-                    (byte) 0x57, (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74,
-                    (byte) 0x73, (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20,
-                    (byte) 0x4C, (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x1E, (byte) 0x17,
-                    (byte) 0x0D, (byte) 0x31, (byte) 0x34, (byte) 0x30, (byte) 0x34, (byte) 0x32,
-                    (byte) 0x33, (byte) 0x32, (byte) 0x30, (byte) 0x35, (byte) 0x30, (byte) 0x34,
-                    (byte) 0x30, (byte) 0x5A, (byte) 0x17, (byte) 0x0D, (byte) 0x31, (byte) 0x37,
-                    (byte) 0x30, (byte) 0x34, (byte) 0x32, (byte) 0x32, (byte) 0x32, (byte) 0x30,
-                    (byte) 0x35, (byte) 0x30, (byte) 0x34, (byte) 0x30, (byte) 0x5A, (byte) 0x30,
-                    (byte) 0x45, (byte) 0x31, (byte) 0x0B, (byte) 0x30, (byte) 0x09, (byte) 0x06,
-                    (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02,
-                    (byte) 0x41, (byte) 0x55, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11,
-                    (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0C,
-                    (byte) 0x0A, (byte) 0x53, (byte) 0x6F, (byte) 0x6D, (byte) 0x65, (byte) 0x2D,
-                    (byte) 0x53, (byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31,
-                    (byte) 0x21, (byte) 0x30, (byte) 0x1F, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-                    (byte) 0x04, (byte) 0x0A, (byte) 0x0C, (byte) 0x18, (byte) 0x49, (byte) 0x6E,
-                    (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x6E, (byte) 0x65, (byte) 0x74,
-                    (byte) 0x20, (byte) 0x57, (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69,
-                    (byte) 0x74, (byte) 0x73, (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79,
-                    (byte) 0x20, (byte) 0x4C, (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x81,
-                    (byte) 0x9F, (byte) 0x30, (byte) 0x0D, (byte) 0x06, (byte) 0x09, (byte) 0x2A,
-                    (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xF7, (byte) 0x0D, (byte) 0x01,
-                    (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81,
-                    (byte) 0x8D, (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, (byte) 0x02,
-                    (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xD8, (byte) 0x2B, (byte) 0xC8,
-                    (byte) 0xA6, (byte) 0x32, (byte) 0xE4, (byte) 0x62, (byte) 0xFF, (byte) 0x4D,
-                    (byte) 0xF3, (byte) 0xD0, (byte) 0xAD, (byte) 0x59, (byte) 0x8B, (byte) 0x45,
-                    (byte) 0xA7, (byte) 0xBD, (byte) 0xF1, (byte) 0x47, (byte) 0xBF, (byte) 0x09,
-                    (byte) 0x58, (byte) 0x7B, (byte) 0x22, (byte) 0xBD, (byte) 0x35, (byte) 0xAE,
-                    (byte) 0x97, (byte) 0x25, (byte) 0x86, (byte) 0x94, (byte) 0xA0, (byte) 0x80,
-                    (byte) 0xC0, (byte) 0xB4, (byte) 0x1F, (byte) 0x76, (byte) 0x91, (byte) 0x67,
-                    (byte) 0x46, (byte) 0x31, (byte) 0xD0, (byte) 0x10, (byte) 0x84, (byte) 0xB7,
-                    (byte) 0x22, (byte) 0x1E, (byte) 0x70, (byte) 0x23, (byte) 0x91, (byte) 0x72,
-                    (byte) 0xC8, (byte) 0xE9, (byte) 0x6D, (byte) 0x79, (byte) 0x3A, (byte) 0x85,
-                    (byte) 0x77, (byte) 0x80, (byte) 0x0F, (byte) 0xC4, (byte) 0x95, (byte) 0x16,
-                    (byte) 0x75, (byte) 0xC5, (byte) 0x4A, (byte) 0x71, (byte) 0x4C, (byte) 0xC8,
-                    (byte) 0x63, (byte) 0x3F, (byte) 0xA3, (byte) 0xF2, (byte) 0x63, (byte) 0x9C,
-                    (byte) 0x2A, (byte) 0x4F, (byte) 0x9A, (byte) 0xFA, (byte) 0xCB, (byte) 0xC1,
-                    (byte) 0x71, (byte) 0x6E, (byte) 0x28, (byte) 0x85, (byte) 0x28, (byte) 0xA0,
-                    (byte) 0x27, (byte) 0x1E, (byte) 0x65, (byte) 0x1C, (byte) 0xAE, (byte) 0x07,
-                    (byte) 0xD5, (byte) 0x5B, (byte) 0x6F, (byte) 0x2D, (byte) 0x43, (byte) 0xED,
-                    (byte) 0x2B, (byte) 0x90, (byte) 0xB1, (byte) 0x8C, (byte) 0xAF, (byte) 0x24,
-                    (byte) 0x6D, (byte) 0xAE, (byte) 0xE9, (byte) 0x17, (byte) 0x3A, (byte) 0x05,
-                    (byte) 0xC1, (byte) 0xBF, (byte) 0xB8, (byte) 0x1C, (byte) 0xAE, (byte) 0x65,
-                    (byte) 0x3B, (byte) 0x1B, (byte) 0x58, (byte) 0xC2, (byte) 0xD9, (byte) 0xAE,
-                    (byte) 0xD6, (byte) 0xAA, (byte) 0x67, (byte) 0x88, (byte) 0xF1, (byte) 0x02,
-                    (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xA3, (byte) 0x50,
-                    (byte) 0x30, (byte) 0x4E, (byte) 0x30, (byte) 0x1D, (byte) 0x06, (byte) 0x03,
-                    (byte) 0x55, (byte) 0x1D, (byte) 0x0E, (byte) 0x04, (byte) 0x16, (byte) 0x04,
-                    (byte) 0x14, (byte) 0x8B, (byte) 0x75, (byte) 0xD5, (byte) 0xAC, (byte) 0xCB,
-                    (byte) 0x08, (byte) 0xBE, (byte) 0x0E, (byte) 0x1F, (byte) 0x65, (byte) 0xB7,
-                    (byte) 0xFA, (byte) 0x56, (byte) 0xBE, (byte) 0x6C, (byte) 0xA7, (byte) 0x75,
-                    (byte) 0xDA, (byte) 0x85, (byte) 0xAF, (byte) 0x30, (byte) 0x1F, (byte) 0x06,
-                    (byte) 0x03, (byte) 0x55, (byte) 0x1D, (byte) 0x23, (byte) 0x04, (byte) 0x18,
-                    (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x8B, (byte) 0x75,
-                    (byte) 0xD5, (byte) 0xAC, (byte) 0xCB, (byte) 0x08, (byte) 0xBE, (byte) 0x0E,
-                    (byte) 0x1F, (byte) 0x65, (byte) 0xB7, (byte) 0xFA, (byte) 0x56, (byte) 0xBE,
-                    (byte) 0x6C, (byte) 0xA7, (byte) 0x75, (byte) 0xDA, (byte) 0x85, (byte) 0xAF,
-                    (byte) 0x30, (byte) 0x0C, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1D,
-                    (byte) 0x13, (byte) 0x04, (byte) 0x05, (byte) 0x30, (byte) 0x03, (byte) 0x01,
-                    (byte) 0x01, (byte) 0xFF, (byte) 0x30, (byte) 0x0D, (byte) 0x06, (byte) 0x09,
-                    (byte) 0x2A, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xF7, (byte) 0x0D,
-                    (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03,
-                    (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0x3B, (byte) 0xE8, (byte) 0x78,
-                    (byte) 0x6D, (byte) 0x95, (byte) 0xD6, (byte) 0x3D, (byte) 0x6A, (byte) 0xF7,
-                    (byte) 0x13, (byte) 0x19, (byte) 0x2C, (byte) 0x1B, (byte) 0xC2, (byte) 0x88,
-                    (byte) 0xAE, (byte) 0x22, (byte) 0xAB, (byte) 0xF4, (byte) 0x8D, (byte) 0x32,
-                    (byte) 0xF5, (byte) 0x7C, (byte) 0x71, (byte) 0x67, (byte) 0xCF, (byte) 0x2D,
-                    (byte) 0xD1, (byte) 0x1C, (byte) 0xC2, (byte) 0xC3, (byte) 0x87, (byte) 0xE2,
-                    (byte) 0xE9, (byte) 0xBE, (byte) 0x89, (byte) 0x5C, (byte) 0xE4, (byte) 0x34,
-                    (byte) 0xAB, (byte) 0x48, (byte) 0x91, (byte) 0xC2, (byte) 0x3F, (byte) 0x95,
-                    (byte) 0xAE, (byte) 0x2B, (byte) 0x47, (byte) 0x9E, (byte) 0x25, (byte) 0x78,
-                    (byte) 0x6B, (byte) 0x4F, (byte) 0x9A, (byte) 0x10, (byte) 0xA4, (byte) 0x72,
-                    (byte) 0xFD, (byte) 0xCF, (byte) 0xF7, (byte) 0x02, (byte) 0x0C, (byte) 0xB0,
-                    (byte) 0x0A, (byte) 0x08, (byte) 0xA4, (byte) 0x5A, (byte) 0xE2, (byte) 0xE5,
-                    (byte) 0x74, (byte) 0x7E, (byte) 0x11, (byte) 0x1D, (byte) 0x39, (byte) 0x60,
-                    (byte) 0x6A, (byte) 0xC9, (byte) 0x1F, (byte) 0x69, (byte) 0xF3, (byte) 0x2E,
-                    (byte) 0x63, (byte) 0x26, (byte) 0xDC, (byte) 0x9E, (byte) 0xEF, (byte) 0x6B,
-                    (byte) 0x7A, (byte) 0x0A, (byte) 0xE1, (byte) 0x54, (byte) 0x57, (byte) 0x98,
-                    (byte) 0xAA, (byte) 0x72, (byte) 0x91, (byte) 0x78, (byte) 0x04, (byte) 0x7E,
-                    (byte) 0x1F, (byte) 0x8F, (byte) 0x65, (byte) 0x4D, (byte) 0x1F, (byte) 0x0B,
-                    (byte) 0x12, (byte) 0xAC, (byte) 0x9C, (byte) 0x24, (byte) 0x0F, (byte) 0x84,
-                    (byte) 0x14, (byte) 0x1A, (byte) 0x55, (byte) 0x2D, (byte) 0x1F, (byte) 0xBB,
-                    (byte) 0xF0, (byte) 0x9D, (byte) 0x09, (byte) 0xB2, (byte) 0x08, (byte) 0x5C,
-                    (byte) 0x59, (byte) 0x32, (byte) 0x65, (byte) 0x80, (byte) 0x26};
-
-    private static final byte[] DUMMY_OCSP_DATA = new byte[1];
-
-    private static final byte[] DUMMY_TLS_SCT_DATA = new byte[1];
-
-    private static ClientSessionContext clientCtx;
-
-    @BeforeClass
-    public static void setup() {
-        clientCtx = new ClientSessionContext();
+    @Before
+    public void setup() {
+        context = newContext();
     }
 
-    @After
-    public void tearDown() throws Exception {
-        assertEquals(0, NativeCrypto.ERR_peek_last_error());
-    }
+    abstract T newContext();
+    abstract int size(T context);
+    abstract SslSessionWrapper getCachedSession(T context, SslSessionWrapper s);
 
-    private static TestSessionBuilder getType1() {
-        return new TestSessionBuilder()
-                .setType(0x01)
-                .setSessionData(kOpenSSLSession)
-                .addCertificate(DUMMY_CERT);
-    }
+    @Test
+    public void testSimpleAddition() {
+        SslSessionWrapper a = newSession("a");
+        SslSessionWrapper b = newSession("b");
 
-    private static TestSessionBuilder getType2() {
-        return new TestSessionBuilder()
-                .setType(0x02)
-                .setSessionData(kOpenSSLSession)
-                .addCertificate(DUMMY_CERT)
-                .addOcspData(DUMMY_OCSP_DATA);
-    }
+        context.cacheSession(a);
+        assertSessionContextContents(toArray(a), toArray(b));
 
-    private static TestSessionBuilder getType3() {
-        return new TestSessionBuilder()
-                .setType(0x03)
-                .setSessionData(kOpenSSLSession)
-                .addCertificate(DUMMY_CERT)
-                .addOcspData(DUMMY_OCSP_DATA)
-                .setTlsSctData(DUMMY_TLS_SCT_DATA);
+        context.cacheSession(b);
+        assertSessionContextContents(toArray(a, b), toArray());
     }
 
     @Test
-    public void toSession_EmptyArray_Invalid_Failure() throws Exception {
-        assertInvalidSession(new byte[0]);
+    public void testTrimToSize() {
+        SslSessionWrapper a = newSession("a");
+        SslSessionWrapper b = newSession("b");
+        SslSessionWrapper c = newSession("c");
+        SslSessionWrapper d = newSession("d");
+
+        context.cacheSession(a);
+        context.cacheSession(b);
+        context.cacheSession(c);
+        context.cacheSession(d);
+        assertSessionContextContents(toArray(a, b, c, d), toArray());
+
+        context.setSessionCacheSize(2);
+        assertSessionContextContents(toArray(c, d), toArray(a, b));
     }
 
     @Test
-    public void toSession_Type1_Valid_Success() throws Exception {
-        assertValidSession(getType1().build());
+    public void testImplicitRemovalOfOldest() {
+        context.setSessionCacheSize(2);
+        SslSessionWrapper a = newSession("a");
+        SslSessionWrapper b = newSession("b");
+        SslSessionWrapper c = newSession("c");
+        SslSessionWrapper d = newSession("d");
+
+        context.cacheSession(a);
+        assertSessionContextContents(toArray(a), toArray(b, c, d));
+
+        context.cacheSession(b);
+        assertSessionContextContents(toArray(a, b), toArray(c, d));
+
+        context.cacheSession(c);
+        assertSessionContextContents(toArray(b, c), toArray(a, d));
+
+        context.cacheSession(d);
+        assertSessionContextContents(toArray(c, d), toArray(a, b));
     }
 
     @Test
-    public void toSession_Type2_Valid_Success() throws Exception {
-        assertValidSession(getType2().build());
+    public void testSerializeSession() throws Exception {
+        Certificate mockCert = mock(Certificate.class);
+        when(mockCert.getEncoded()).thenReturn(new byte[] {0x05, 0x06, 0x07, 0x10});
+
+        byte[] encodedBytes = new byte[] {0x01, 0x02, 0x03};
+        SslSessionWrapper session = new MockSessionBuilder()
+                .id(new byte[] {0x11, 0x09, 0x03, 0x20})
+                .host("ssl.example.com")
+                .encodedBytes(encodedBytes)
+                .build();
+
+        SSLClientSessionCache mockCache = mock(SSLClientSessionCache.class);
+        ClientSessionContext context = new ClientSessionContext();
+        context.setPersistentCache(mockCache);
+
+        context.cacheSession(session);
+        verify(mockCache).putSessionData(any(SSLSession.class), same(encodedBytes));
     }
 
-    @Test
-    public void toSession_Type3_Valid_Success() throws Exception {
-        assertValidSession(getType3().build());
-    }
+    private void assertSessionContextContents(
+            SslSessionWrapper[] contains, SslSessionWrapper[] exludes) {
+        assertEquals(contains.length, size(context));
 
-    private void assertTruncatedSessionFails(byte[] validSession) {
-        for (int i = 0; i < validSession.length - 1; i++) {
-            byte[] truncatedSession = new byte[i];
-            System.arraycopy(validSession, 0, truncatedSession, 0, i);
-            assertNull("Truncating to " + i + " bytes of " + validSession.length
-                            + " should not succeed",
-                    clientCtx.toSession(truncatedSession, "www.google.com", 443));
+        for (SslSessionWrapper s : contains) {
+            assertSame(s.getPeerHost(), s, getCachedSession(context, s));
+        }
+        for (SslSessionWrapper s : exludes) {
+            assertNull(s.getPeerHost(), getCachedSession(context, s));
         }
     }
 
-    @Test
-    public void toSession_Type3_Truncated_Failure() throws Exception {
-        assertTruncatedSessionFails(getType3().build());
+    private static SslSessionWrapper[] toArray(SslSessionWrapper... sessions) {
+        return sessions;
     }
 
-    private static void assertValidSession(byte[] data) {
-        assertNotNull(clientCtx.toSession(data, "www.google.com", 443));
-    }
-
-    private static void assertInvalidSession(byte[] data) {
-        assertNull(clientCtx.toSession(data, "www.google.com", 443));
-    }
-
-    @Test
-    public void toSession_UnknownType_Failure() throws Exception {
-        assertInvalidSession(getType3().setType((byte) 0xEE).build());
-    }
-
-    @Test
-    public void toSession_CertificatesCountTooLarge_Failure() throws Exception {
-        assertInvalidSession(getType3().setCertificatesLength(16834).build());
-    }
-
-    @Test
-    public void toSession_CertificatesCountNegative_Failure() throws Exception {
-        assertInvalidSession(getType3().setCertificatesLength(-1).build());
-    }
-
-    @Test
-    public void toSession_CertificateSizeNegative_Failure() throws Exception {
-        assertInvalidSession(getType3().setCertificateLength(0, -1).build());
-    }
-
-    @Test
-    public void toSession_CertificateSizeTooLarge_Failure() throws Exception {
-        assertInvalidSession(getType3().setCertificateLength(0, 16834).build());
-    }
-
-    @Test
-    public void toSession_SessionDataSizeTooLarge_Failure() throws Exception {
-        assertInvalidSession(getType3().setSessionDataLength(16834).build());
-    }
-
-    @Test
-    public void toSession_SessionDataSizeNegative_Failure() throws Exception {
-        assertInvalidSession(getType3().setSessionDataLength(-1).build());
-    }
-
-    @Test
-    public void toSession_OcspDatasNumberTooMany_Failure() throws Exception {
-        assertInvalidSession(getType3().setOcspDatasLength(32791).build());
-    }
-
-    @Test
-    public void toSession_OcspDatasNumberNegative_Failure() throws Exception {
-        assertInvalidSession(getType3().setOcspDatasLength(-1).build());
-    }
-
-    @Test
-    public void toSession_OcspDataSizeNegative_Failure() throws Exception {
-        assertInvalidSession(getType3().setOcspDataLength(0, -1).build());
-    }
-
-    @Test
-    public void toSession_OcspDataSizeTooLarge_Failure() throws Exception {
-        assertInvalidSession(getType3().setOcspDataLength(0, 92948).build());
-    }
-
-    @Test
-    public void toSession_TlsSctDataSizeNegative_Failure() throws Exception {
-        assertInvalidSession(getType3().setTlsSctDataLength(-1).build());
-    }
-
-    @Test
-    public void toSession_TlsSctDataSizeTooLarge_Failure() throws Exception {
-        assertInvalidSession(getType3().setTlsSctDataLength(931148).build());
-    }
-
-    @Test
-    public void toSession_Type2OcspDataEmpty_Success() throws Exception {
-        assertValidSession(getType1().setType(0x02).setOcspDataEmpty().build());
-    }
-
-    @Test
-    public void toSession_Type3TlsSctDataEmpty_Success() throws Exception {
-        assertValidSession(getType2().setType(0x03).setTlsSctDataEmpty().build());
-    }
-
-    @Test
-    public void toSession_Type3OcspAndTlsSctDataEmpty_Success() throws Exception {
-        assertValidSession(
-                getType1().setType(0x03).setOcspDataEmpty().setTlsSctDataEmpty().build());
-    }
-
-    private static void assertTrailingDataFails(byte[] validSession) {
-        byte[] invalidSession = new byte[validSession.length + 1];
-        System.arraycopy(validSession, 0, invalidSession, 0, validSession.length);
-        assertInvalidSession(invalidSession);
-    }
-
-    @Test
-    public void toSession_Type1TrailingData_Failure() throws Exception {
-        assertTrailingDataFails(getType1().build());
-    }
-
-    @Test
-    public void toSession_Type2TrailingData_Failure() throws Exception {
-        assertTrailingDataFails(getType2().build());
-    }
-
-    @Test
-    public void toSession_Type3TrailingData_Failure() throws Exception {
-        assertTrailingDataFails(getType3().build());
-    }
-
-    @Test
-    public void test_reserializableFromByteArray_roundTrip_type1() throws Exception {
-        // Converting OPEN_SSL (type 1) -> OPEN_SSL_WITH_TLS_SCT (type 3) adds
-        // eight zero-bytes:
-        //  1.) 4 bytes for int32 value 0 == countOcspResponses
-        //  2.) 4 bytes for int32 value 0 == tlsSctDataLength
-        // since OPEN_SSL (type 1) cannot contain OSCP or TLS SCT data.
-        check_reserializableFromByteArray_roundTrip(getType1().build(), new byte[8]);
-    }
-
-    @Test
-    public void test_reserializableFromByteArray_roundTrip_type2() throws Exception {
-        // Converting OPEN_SSL_WITH_OCSP (type 2) -> OPEN_SSL_WITH_TLS_SCT (type 3) adds
-        // four zero-bytes for int32 value 0 == tlsSctDataLength
-        // since OPEN_SSL_WITH_OCSP (type 2) cannot contain TLS SCT data.
-        check_reserializableFromByteArray_roundTrip(getType2().build(), new byte[4]);
-    }
-
-    @Test
-    public void test_reserializableFromByteArray_roundTrip_type3() throws Exception {
-        check_reserializableFromByteArray_roundTrip(getType3().build(), new byte[0]);
-    }
-
-    private static void check_reserializableFromByteArray_roundTrip(
-            byte[] data, byte[] expectedTrailingBytesAfterReserialization) throws Exception {
-        SSLSession session = clientCtx.toSession(data, "www.example.com", 12345);
-        byte[] sessionBytes = clientCtx.toBytes(session);
-
-        SSLSession session2 = clientCtx.toSession(sessionBytes, "www.example.com", 12345);
-        byte[] sessionBytes2 = clientCtx.toBytes(session);
-
-        assertSSLSessionEquals(session, session2);
-        assertByteArrayEquals(sessionBytes, sessionBytes2);
-
-        assertEquals("www.example.com", session.getPeerHost());
-        assertEquals(12345, session.getPeerPort());
-        assertTrue(sessionBytes.length >= data.length);
-
-        byte[] expectedReserializedData = concat(data, expectedTrailingBytesAfterReserialization);
-        // AbstractSessionContext.toBytes() always writes type 3 == OPEN_SSL_WITH_TLS_SCT
-        expectedReserializedData[3] = 3;
-        assertByteArrayEquals(expectedReserializedData, sessionBytes);
-    }
-
-    private static byte[] concat(byte[] a, byte[] b) {
-        byte[] result = new byte[a.length + b.length];
-        System.arraycopy(a, 0, result, 0, a.length);
-        System.arraycopy(b, 0, result, a.length, b.length);
-        return result;
-    }
-
-    private static void assertSSLSessionEquals(SSLSession a, SSLSession b) throws Exception {
-        assertEquals(a.getApplicationBufferSize(), b.getApplicationBufferSize());
-        assertEquals(a.getCipherSuite(), b.getCipherSuite());
-        assertEquals(a.getCreationTime(), b.getCreationTime());
-        assertByteArrayEquals(a.getId(), b.getId());
-        assertEquals(a.getLastAccessedTime(), b.getLastAccessedTime());
-        assertArrayEquals(a.getLocalCertificates(), b.getLocalCertificates());
-        assertEquals(a.getLocalPrincipal(), b.getLocalPrincipal());
-        assertArrayEquals(a.getPeerCertificateChain(), b.getPeerCertificateChain());
-        assertArrayEquals(a.getPeerCertificates(), b.getPeerCertificates());
-        assertEquals(a.getPeerHost(), b.getPeerHost());
-        assertEquals(a.getPeerPort(), b.getPeerPort());
-        assertEquals(a.getPeerPrincipal(), b.getPeerPrincipal());
-        assertEquals(a.getProtocol(), b.getProtocol());
-        assertEquals(getValueMap(a), getValueMap(b));
-        assertEquals(a.isValid(), b.isValid());
-
-        assertEquals(a.getClass(), b.getClass());
-
-        // Could potentially cast to AbstractOpenSSLSession here and compare additional fields.
-    }
-
-    private static Map<String, Object> getValueMap(SSLSession sslSession) {
-        Map<String, Object> result = new HashMap<>();
-        for (String valueName : sslSession.getValueNames()) {
-            result.put(valueName, sslSession.getValue(valueName));
-        }
-        return Collections.unmodifiableMap(result);
-    }
-
-    private static <T> void assertArrayEquals(T[] expected, T[] actual) {
-        assertTrue("Expected " + Arrays.toString(expected) + ", got " + Arrays.toString(actual),
-                Arrays.equals(expected, actual));
-    }
-
-    private static void assertByteArrayEquals(byte[] expected, byte[] actual) {
-        // If running on OpenJDK 8+, could use java.util.Base64 for better failure messages:
-        // assertEquals(Base64.encode(expected), Base64.encode(actual));
-        assertTrue("Expected " + Arrays.toString(expected) + ", got " + Arrays.toString(actual),
-                Arrays.equals(expected, actual));
+    private SslSessionWrapper newSession(String host) {
+        return new MockSessionBuilder().host(host).build();
     }
 }
diff --git a/openjdk/src/test/java/org/conscrypt/ClientSessionContextTest.java b/openjdk/src/test/java/org/conscrypt/ClientSessionContextTest.java
index f214d7b..709b893 100644
--- a/openjdk/src/test/java/org/conscrypt/ClientSessionContextTest.java
+++ b/openjdk/src/test/java/org/conscrypt/ClientSessionContextTest.java
@@ -16,135 +16,36 @@
 
 package org.conscrypt;
 
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.conscrypt.MockSessionBuilder.DEFAULT_PORT;
 
-import java.security.cert.Certificate;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Set;
-import javax.net.ssl.SSLSession;
-import junit.framework.TestCase;
-import libcore.javax.net.ssl.FakeSSLSession;
+import java.security.KeyManagementException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
-public final class ClientSessionContextTest extends TestCase {
+@RunWith(JUnit4.class)
+public class ClientSessionContextTest extends AbstractSessionContextTest<ClientSessionContext> {
 
-    public void testSimpleAddition() {
-        ClientSessionContext context = new ClientSessionContext();
-        SSLSession a = new ValidSSLSession("a");
-        SSLSession b = new ValidSSLSession("b");
-
-        context.putSession(a);
-        assertSessionContextContents(context, new SSLSession[] { a }, new SSLSession[] { b });
-
-        context.putSession(b);
-        assertSessionContextContents(context, new SSLSession[] { a, b }, new SSLSession[0]);
+    @Override
+    ClientSessionContext newContext() {
+        return new ClientSessionContext();
     }
 
-    public void testTrimToSize() {
-        ClientSessionContext context = new ClientSessionContext();
-        ValidSSLSession a = new ValidSSLSession("a");
-        ValidSSLSession b = new ValidSSLSession("b");
-        ValidSSLSession c = new ValidSSLSession("c");
-        ValidSSLSession d = new ValidSSLSession("d");
-
-        context.putSession(a);
-        assertSessionContextContents(context, new SSLSession[] { a }, new SSLSession[] { b, c, d });
-
-        context.putSession(b);
-        assertSessionContextContents(context, new SSLSession[] { a, b }, new SSLSession[] { c, d });
-
-        context.putSession(c);
-        assertSessionContextContents(context, new SSLSession[] { a, b, c }, new SSLSession[] { d });
-
-        context.putSession(d);
-        assertSessionContextContents(context, new SSLSession[] { a, b, c, d }, new SSLSession[0]);
-
-        context.setSessionCacheSize(2);
-        assertSessionContextContents(context, new SSLSession[] { c, d }, new SSLSession[] { a, b });
+    @Override
+    SslSessionWrapper getCachedSession(ClientSessionContext context, SslSessionWrapper s) {
+        return context.getCachedSession(s.getPeerHost(), DEFAULT_PORT,
+                getDefaultSSLParameters());
     }
 
-    public void testImplicitRemovalOfOldest() {
-        ClientSessionContext context = new ClientSessionContext();
-        context.setSessionCacheSize(2);
-        ValidSSLSession a = new ValidSSLSession("a");
-        ValidSSLSession b = new ValidSSLSession("b");
-        ValidSSLSession c = new ValidSSLSession("c");
-        ValidSSLSession d = new ValidSSLSession("d");
-
-        context.putSession(a);
-        assertSessionContextContents(context, new SSLSession[] { a }, new SSLSession[] { b, c, d });
-
-        context.putSession(b);
-        assertSessionContextContents(context, new SSLSession[] { a, b }, new SSLSession[] { c, d });
-
-        context.putSession(c);
-        assertSessionContextContents(context, new SSLSession[] { b, c }, new SSLSession[] { a, d });
-
-        context.putSession(d);
-        assertSessionContextContents(context, new SSLSession[] { c, d }, new SSLSession[] { a, b });
+    @Override
+    int size(ClientSessionContext context) {
+        return context.size();
     }
 
-    public void testSerializeSession_NoStatusResponses() throws Exception {
-        OpenSSLSessionImpl mockSession = mock(OpenSSLSessionImpl.class);
-        when(mockSession.getId()).thenReturn(new byte[] { 0x11, 0x09, 0x03, 0x20 });
-        when(mockSession.getPeerHost()).thenReturn("ssl.example.com");
-        when(mockSession.getPeerPort()).thenReturn(443);
-        when(mockSession.getEncoded()).thenReturn(new byte[] { 0x01, 0x02, 0x03 });
-        when(mockSession.getStatusResponses()).thenReturn(Collections.<byte[]>emptyList());
-
-        Certificate mockCert = mock(Certificate.class);
-        when(mockCert.getEncoded()).thenReturn(new byte[] { 0x05, 0x06, 0x07, 0x10 });
-
-        when(mockSession.getPeerCertificates()).thenReturn(new Certificate[] { mockCert });
-
-        SSLClientSessionCache mockCache = mock(SSLClientSessionCache.class);
-        ClientSessionContext context = new ClientSessionContext();
-        context.setPersistentCache(mockCache);
-
-        context.putSession(mockSession);
-        verify(mockCache).putSessionData(eq(mockSession), any(byte[].class));
-    }
-
-
-    private static void assertSessionContextContents(ClientSessionContext context,
-                                                     SSLSession[] contains,
-                                                     SSLSession[] exludes) {
-        assertEquals(contains.length, context.size());
-
-        for (SSLSession s : contains) {
-            assertSame(s.getPeerHost(), s, context.getSession(s.getId()));
-            assertSame(s.getPeerHost(), s, context.getSession(s.getPeerHost(), 443));
-        }
-        for (SSLSession s : exludes) {
-            assertNull(s.getPeerHost(), context.getSession(s.getId()));
-            assertNull(s.getPeerHost(), context.getSession(s.getPeerHost(), 443));
-        }
-
-        Set<SSLSession> sessions = new HashSet<SSLSession>();
-        Enumeration<byte[]> ids = context.getIds();
-        while (ids.hasMoreElements()) {
-            byte[] id = ids.nextElement();
-            sessions.add(context.getSession(id));
-        }
-
-        Set<SSLSession> expected = new HashSet<SSLSession>();
-        for (SSLSession s : sessions) {
-            expected.add(s);
-        }
-        assertEquals(expected, sessions);
-    }
-
-    static class ValidSSLSession extends FakeSSLSession {
-        ValidSSLSession(String host) {
-            super(host);
-        }
-        @Override public boolean isValid() {
-            return true;
+    private static SSLParametersImpl getDefaultSSLParameters() {
+        try {
+            return SSLParametersImpl.getDefault();
+        } catch (KeyManagementException e) {
+            throw new RuntimeException(e);
         }
     }
 }
diff --git a/openjdk/src/test/java/org/conscrypt/MockSessionBuilder.java b/openjdk/src/test/java/org/conscrypt/MockSessionBuilder.java
new file mode 100644
index 0000000..5fb4caa
--- /dev/null
+++ b/openjdk/src/test/java/org/conscrypt/MockSessionBuilder.java
@@ -0,0 +1,85 @@
+/*
+ * 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.TestUtils.UTF_8;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Utility class for constructing mock sessions.
+ */
+final class MockSessionBuilder {
+    static final String DEFAULT_CIPHER_SUITE = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
+    static final String DEFAULT_PROTOCOL = TestUtils.PROTOCOL_TLS_V1_2;
+    static final int DEFAULT_PORT = 443;
+
+    private byte[] id;
+    private boolean valid = true;
+    private String host;
+    private int port = DEFAULT_PORT;
+    private String cipherSuite = DEFAULT_CIPHER_SUITE;
+    private String protocol = DEFAULT_PROTOCOL;
+    private byte[] encodedBytes = EmptyArray.BYTE;
+
+    MockSessionBuilder id(byte[] id) {
+        this.id = id;
+        return this;
+    }
+
+    MockSessionBuilder protocol(String protocol) {
+        this.protocol = protocol;
+        return this;
+    }
+
+    MockSessionBuilder host(String host) {
+        this.host = host;
+        return this;
+    }
+
+    MockSessionBuilder port(int port) {
+        this.port = port;
+        return this;
+    }
+
+    MockSessionBuilder valid(boolean valid) {
+        this.valid = valid;
+        return this;
+    }
+
+    MockSessionBuilder cipherSuite(String cipherSuite) {
+        this.cipherSuite = cipherSuite;
+        return this;
+    }
+
+    MockSessionBuilder encodedBytes(byte[] encodedBytes) {
+        this.encodedBytes = encodedBytes;
+        return this;
+    }
+
+    SslSessionWrapper build() {
+        SslSessionWrapper session = mock(SslSessionWrapper.class);
+        byte[] id = this.id == null ? host.getBytes(UTF_8) : this.id;
+        when(session.getId()).thenReturn(id);
+        when(session.isValid()).thenReturn(valid);
+        when(session.getProtocol()).thenReturn(protocol);
+        when(session.getPeerHost()).thenReturn(host);
+        when(session.getPeerPort()).thenReturn(port);
+        when(session.getCipherSuite()).thenReturn(cipherSuite);
+        when(session.toBytes()).thenReturn(encodedBytes);
+        return session;
+    }
+}
diff --git a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java
index 66faca4..62594bf 100644
--- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java
+++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java
@@ -35,6 +35,7 @@
 import java.io.UnsupportedEncodingException;
 import java.lang.reflect.Method;
 import java.math.BigInteger;
+import java.net.InetAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.SocketException;
@@ -53,6 +54,7 @@
 import java.security.spec.ECPrivateKeySpec;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.Callable;
@@ -251,6 +253,14 @@
         NativeCrypto.EVP_PKEY_cmp(null, null);
     }
 
+    @Test(expected = NullPointerException.class)
+    public void EVP_PKEY_cmp_withNullShouldThrow() throws Exception {
+        RSAPrivateCrtKey privKey1 = generateRsaKey();
+        NativeRef.EVP_PKEY pkey1 = getRsaPkey(privKey1);
+        assertNotSame(NULL, pkey1);
+        NativeCrypto.EVP_PKEY_cmp(pkey1, null);
+    }
+
     @Test
     public void test_EVP_PKEY_cmp() throws Exception {
         RSAPrivateCrtKey privKey1 = generateRsaKey();
@@ -264,12 +274,6 @@
         NativeRef.EVP_PKEY pkey2 = getRsaPkey(generateRsaKey());
         assertNotSame(NULL, pkey2);
 
-        try {
-            NativeCrypto.EVP_PKEY_cmp(pkey1, null);
-            fail("Should throw NullPointerException when arguments are NULL");
-        } catch (NullPointerException expected) {
-        }
-
         assertEquals("Same keys should be the equal", 1, NativeCrypto.EVP_PKEY_cmp(pkey1, pkey1));
 
         assertEquals(
@@ -305,7 +309,7 @@
     }
 
     @Test(expected = NullPointerException.class)
-    public void SSL_CTX_set_session_id_context_NullSessionIdArgument() throws Exception {
+    public void SSL_CTX_set_session_id_context_withNullShouldThrow() throws Exception {
         long c = NativeCrypto.SSL_CTX_new();
         try {
             NativeCrypto.SSL_CTX_set_session_id_context(c, null);
@@ -314,6 +318,16 @@
         }
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void test_SSL_CTX_set_session_id_context_withInvalidIdShouldThrow() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        try {
+            NativeCrypto.SSL_CTX_set_session_id_context(c, new byte[33]);
+        } finally {
+            NativeCrypto.SSL_CTX_free(c);
+        }
+    }
+
     @Test
     public void test_SSL_CTX_set_session_id_context() throws Exception {
         byte[] empty = new byte[0];
@@ -322,12 +336,6 @@
         try {
             NativeCrypto.SSL_CTX_set_session_id_context(c, empty);
             NativeCrypto.SSL_CTX_set_session_id_context(c, new byte[32]);
-            try {
-                NativeCrypto.SSL_CTX_set_session_id_context(c, new byte[33]);
-                fail("Expected IllegalArgumentException");
-            } catch (IllegalArgumentException expected) {
-                // Expected.
-            }
         } finally {
             NativeCrypto.SSL_CTX_free(c);
         }
@@ -357,42 +365,55 @@
         NativeCrypto.SSL_use_certificate(NULL, null);
     }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_use_certificate_withNullShouldThrow() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+        try {
+            NativeCrypto.SSL_use_certificate(s, null);
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
+        }
+    }
+
     @Test
     public void test_SSL_use_certificate() throws Exception {
         long c = NativeCrypto.SSL_CTX_new();
         long s = NativeCrypto.SSL_new(c);
 
-        try {
-            NativeCrypto.SSL_use_certificate(s, null);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
         NativeCrypto.SSL_use_certificate(s, getServerCertificates());
 
         NativeCrypto.SSL_free(s);
         NativeCrypto.SSL_CTX_free(c);
     }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_set1_tls_channel_id_withNullChannelShouldThrow() throws Exception {
+        NativeCrypto.SSL_set1_tls_channel_id(NULL, null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void SSL_set1_tls_channel_id_withNullKeyShouldThrow() throws Exception {
+        initChannelIdKey();
+
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+        try {
+            NativeCrypto.SSL_set1_tls_channel_id(s, null);
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
+        }
+    }
+
     @Test
     public void test_SSL_use_PrivateKey_for_tls_channel_id() throws Exception {
         initChannelIdKey();
 
-        try {
-            NativeCrypto.SSL_set1_tls_channel_id(NULL, null);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
         long c = NativeCrypto.SSL_CTX_new();
         long s = NativeCrypto.SSL_new(c);
 
-        try {
-            NativeCrypto.SSL_set1_tls_channel_id(s, null);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
         // Use the key natively. This works because the initChannelIdKey method ensures that the
         // key is backed by OpenSSL.
         NativeCrypto.SSL_set1_tls_channel_id(s, CHANNEL_ID_PRIVATE_KEY.getNativeRef());
@@ -401,21 +422,21 @@
         NativeCrypto.SSL_CTX_free(c);
     }
 
-    @Test
-    public void test_SSL_use_PrivateKey() throws Exception {
-        try {
-            NativeCrypto.SSL_use_PrivateKey(NULL, null);
-            fail();
-        } catch (NullPointerException expected) {
-        }
+    @Test(expected = NullPointerException.class)
+    public void SSL_use_PrivateKey_withNullSslShouldThrow() throws Exception {
+        NativeCrypto.SSL_use_PrivateKey(NULL, null);
+    }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_use_PrivateKeyWithNullKeyShouldThrow() throws Exception {
         long c = NativeCrypto.SSL_CTX_new();
         long s = NativeCrypto.SSL_new(c);
 
         try {
             NativeCrypto.SSL_use_PrivateKey(s, null);
-            fail();
-        } catch (NullPointerException expected) {
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
         }
 
         NativeCrypto.SSL_use_PrivateKey(s, getServerPrivateKey().getNativeRef());
@@ -424,63 +445,74 @@
         NativeCrypto.SSL_CTX_free(c);
     }
 
+    @Test
+    public void test_SSL_use_PrivateKey() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+
+        NativeCrypto.SSL_use_PrivateKey(s, getServerPrivateKey().getNativeRef());
+
+        NativeCrypto.SSL_free(s);
+        NativeCrypto.SSL_CTX_free(c);
+    }
+
     @Test(expected = NullPointerException.class)
-    public void SSL_check_private_key_NullArgument() throws Exception {
+    public void SSL_check_private_key_withNullShouldThrow() throws Exception {
         NativeCrypto.SSL_check_private_key(NULL);
     }
 
-    @Test
-    public void test_SSL_check_private_key_no_key_no_cert() throws Exception {
+    @Test(expected = SSLException.class)
+    public void SSL_check_private_key_withNoKeyOrCertShouldThrow() throws Exception {
         long c = NativeCrypto.SSL_CTX_new();
         long s = NativeCrypto.SSL_new(c);
 
         // neither private or certificate set
         try {
             NativeCrypto.SSL_check_private_key(s);
-            fail();
-        } catch (SSLException expected) {
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
         }
-
-        NativeCrypto.SSL_free(s);
-        NativeCrypto.SSL_CTX_free(c);
     }
 
-    @Test
-    public void test_SSL_check_private_key_cert_then_key() throws Exception {
+    @Test(expected = SSLException.class)
+    public void SSL_check_private_key_withNoKeyShouldThrow() throws Exception {
         long c = NativeCrypto.SSL_CTX_new();
         long s = NativeCrypto.SSL_new(c);
 
-        // first certificate, then private
+        // Certificate but no private key
         NativeCrypto.SSL_use_certificate(s, getServerCertificates());
 
         try {
             NativeCrypto.SSL_check_private_key(s);
-            fail();
-        } catch (SSLException expected) {
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
         }
-
-        NativeCrypto.SSL_use_PrivateKey(s, getServerPrivateKey().getNativeRef());
-        NativeCrypto.SSL_check_private_key(s);
-
-        NativeCrypto.SSL_free(s);
-        NativeCrypto.SSL_CTX_free(c);
     }
 
-    @Test
-    public void test_SSL_check_private_key_key_then_cert() throws Exception {
+    @Test(expected = SSLException.class)
+    public void test_SSL_check_private_NoCertificateShouldThrow() throws Exception {
         long c = NativeCrypto.SSL_CTX_new();
         long s = NativeCrypto.SSL_new(c);
 
         // first private, then certificate
         NativeCrypto.SSL_use_PrivateKey(s, getServerPrivateKey().getNativeRef());
-
         try {
             NativeCrypto.SSL_check_private_key(s);
-            fail();
-        } catch (SSLException expected) {
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
         }
+    }
+
+    @Test
+    public void test_SSL_check_private_key_certThenKey() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
 
         NativeCrypto.SSL_use_certificate(s, getServerCertificates());
+        NativeCrypto.SSL_use_PrivateKey(s, getServerPrivateKey().getNativeRef());
         NativeCrypto.SSL_check_private_key(s);
 
         NativeCrypto.SSL_free(s);
@@ -488,13 +520,26 @@
     }
 
     @Test
-    public void test_SSL_get_mode() throws Exception {
-        try {
-            NativeCrypto.SSL_get_mode(NULL);
-            fail();
-        } catch (NullPointerException expected) {
-        }
+    public void test_SSL_check_private_key_keyThenCert() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
 
+        // first private, then certificate
+        NativeCrypto.SSL_use_PrivateKey(s, getServerPrivateKey().getNativeRef());
+        NativeCrypto.SSL_use_certificate(s, getServerCertificates());
+        NativeCrypto.SSL_check_private_key(s);
+
+        NativeCrypto.SSL_free(s);
+        NativeCrypto.SSL_CTX_free(c);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void SSL_get_mode_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_get_mode(NULL);
+    }
+
+    @Test
+    public void test_SSL_get_mode() throws Exception {
         long c = NativeCrypto.SSL_CTX_new();
         long s = NativeCrypto.SSL_new(c);
         assertTrue(NativeCrypto.SSL_get_mode(s) != 0);
@@ -502,14 +547,13 @@
         NativeCrypto.SSL_CTX_free(c);
     }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_set_mode_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_set_mode(NULL, 0);
+    }
+
     @Test
     public void test_SSL_set_mode_and_clear_mode() throws Exception {
-        try {
-            NativeCrypto.SSL_set_mode(NULL, 0);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
         long c = NativeCrypto.SSL_CTX_new();
         long s = NativeCrypto.SSL_new(c);
         // check SSL_MODE_ENABLE_FALSE_START on by default for BoringSSL
@@ -529,14 +573,13 @@
         NativeCrypto.SSL_CTX_free(c);
     }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_get_options_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_get_options(NULL);
+    }
+
     @Test
     public void test_SSL_get_options() throws Exception {
-        try {
-            NativeCrypto.SSL_get_options(NULL);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
         long c = NativeCrypto.SSL_CTX_new();
         long s = NativeCrypto.SSL_new(c);
         assertTrue(NativeCrypto.SSL_get_options(s) != 0);
@@ -544,14 +587,13 @@
         NativeCrypto.SSL_CTX_free(c);
     }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_set_options_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_set_options(NULL, 0);
+    }
+
     @Test
     public void test_SSL_set_options() throws Exception {
-        try {
-            NativeCrypto.SSL_set_options(NULL, 0);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
         long c = NativeCrypto.SSL_CTX_new();
         long s = NativeCrypto.SSL_new(c);
         assertTrue((NativeCrypto.SSL_get_options(s) & NativeConstants.SSL_OP_NO_SSLv3) == 0);
@@ -561,14 +603,13 @@
         NativeCrypto.SSL_CTX_free(c);
     }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_clear_options_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_clear_options(NULL, 0);
+    }
+
     @Test
     public void test_SSL_clear_options() throws Exception {
-        try {
-            NativeCrypto.SSL_clear_options(NULL, 0);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
         long c = NativeCrypto.SSL_CTX_new();
         long s = NativeCrypto.SSL_new(c);
         assertTrue((NativeCrypto.SSL_get_options(s) & NativeConstants.SSL_OP_NO_SSLv3) == 0);
@@ -580,32 +621,52 @@
         NativeCrypto.SSL_CTX_free(c);
     }
 
-    @Test
-    public void test_SSL_set_cipher_lists() throws Exception {
-        try {
-            NativeCrypto.SSL_set_cipher_lists(NULL, null);
-            fail("Exception not thrown for null ssl and null list");
-        } catch (NullPointerException expected) {
-        }
+    @Test(expected = NullPointerException.class)
+    public void SSL_set_cipher_lists_withNullSslShouldThrow() throws Exception {
+        NativeCrypto.SSL_set_cipher_lists(NULL, null);
+    }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_set_cipher_lists_withNullCiphersShouldThrow() throws Exception {
         long c = NativeCrypto.SSL_CTX_new();
         long s = NativeCrypto.SSL_new(c);
-
         try {
             NativeCrypto.SSL_set_cipher_lists(s, null);
-            fail("Exception not thrown for null list");
-        } catch (NullPointerException expected) {
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
         }
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void test_SSL_set_cipher_lists_withNullCipherShouldThrow() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+        try {
+            NativeCrypto.SSL_set_cipher_lists(s, new String[] {null});
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
+        }
+    }
+
+    @Test
+    public void SSL_set_cipher_lists_withEmptyCiphersShouldSucceed() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
 
         // Explicitly checking that the empty list is allowed.
         // b/21816861
         NativeCrypto.SSL_set_cipher_lists(s, new String[] {});
 
-        try {
-            NativeCrypto.SSL_set_cipher_lists(s, new String[] {null});
-            fail("Exception not thrown for list with null element");
-        } catch (NullPointerException expected) {
-        }
+        NativeCrypto.SSL_free(s);
+        NativeCrypto.SSL_CTX_free(c);
+    }
+
+    @Test
+    public void SSL_set_cipher_lists_withIllegalCipherShouldThrow() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
 
         // see OpenSSL ciphers man page
         String[] illegals = new String[] {// empty
@@ -620,25 +681,34 @@
                 NativeCrypto.SSL_set_cipher_lists(s, new String[] {illegal});
                 fail("Exception now thrown for illegal cipher: " + illegal);
             } catch (IllegalArgumentException expected) {
+                // Expected.
             }
         }
 
-        List<String> ciphers =
-                new ArrayList<String>(NativeCrypto.OPENSSL_TO_STANDARD_CIPHER_SUITES.keySet());
-        NativeCrypto.SSL_set_cipher_lists(s, ciphers.toArray(new String[ciphers.size()]));
-
         NativeCrypto.SSL_free(s);
         NativeCrypto.SSL_CTX_free(c);
     }
 
     @Test
-    public void test_SSL_set_verify() throws Exception {
-        try {
-            NativeCrypto.SSL_set_verify(NULL, 0);
-            fail();
-        } catch (NullPointerException expected) {
-        }
+    public void SSL_set_cipher_lists_withValidCiphersShouldSucceed() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
 
+        List<String> ciphers =
+                new ArrayList<>(NativeCrypto.OPENSSL_TO_STANDARD_CIPHER_SUITES.keySet());
+        NativeCrypto.SSL_set_cipher_lists(s, ciphers.toArray(new String[ciphers.size()]));
+
+        NativeCrypto.SSL_free(s);
+        NativeCrypto.SSL_CTX_free(c);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void SSL_set_verify_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_set_verify(NULL, 0);
+    }
+
+    @Test
+    public void test_SSL_set_verify() throws Exception {
         long c = NativeCrypto.SSL_CTX_new();
         long s = NativeCrypto.SSL_new(c);
         NativeCrypto.SSL_set_verify(s, NativeCrypto.SSL_VERIFY_NONE);
@@ -653,18 +723,19 @@
     private static final boolean DEBUG = false;
 
     public static class Hooks {
-        protected String negotiatedCipherSuite;
+        String negotiatedCipherSuite;
         private OpenSSLKey channelIdPrivateKey;
-        protected boolean pskEnabled;
-        protected byte[] pskKey;
-        protected List<String> enabledCipherSuites;
+         boolean pskEnabled;
+         byte[] pskKey;
+         List<String> enabledCipherSuites;
 
         /**
-         * @throws SSLException
+         * @throws SSLException if an error occurs creating the context.
          */
         public long getContext() throws SSLException {
             return NativeCrypto.SSL_CTX_new();
         }
+
         public long beforeHandshake(long context) throws SSLException {
             long s = NativeCrypto.SSL_new(context);
             // Limit cipher suites to a known set so authMethod is known.
@@ -700,6 +771,7 @@
                 try {
                     NativeCrypto.SSL_shutdown(ssl, fd, callback);
                 } catch (IOException e) {
+                    // Expected.
                 }
                 NativeCrypto.SSL_free(ssl);
             }
@@ -712,20 +784,20 @@
         }
     }
 
-    public static class TestSSLHandshakeCallbacks implements SSLHandshakeCallbacks {
+    static class TestSSLHandshakeCallbacks implements SSLHandshakeCallbacks {
         private final Socket socket;
         private final long sslNativePointer;
         private final Hooks hooks;
 
-        public TestSSLHandshakeCallbacks(Socket socket, long sslNativePointer, Hooks hooks) {
+        TestSSLHandshakeCallbacks(Socket socket, long sslNativePointer, Hooks hooks) {
             this.socket = socket;
             this.sslNativePointer = sslNativePointer;
             this.hooks = hooks;
         }
 
-        public long[] certificateChainRefs;
-        public String authMethod;
-        public boolean verifyCertificateChainCalled;
+        private long[] certificateChainRefs;
+        private String authMethod;
+        private boolean verifyCertificateChainCalled;
 
         @Override
         public void verifyCertificateChain(long[] certChainRefs, String authMethod)
@@ -741,9 +813,9 @@
             this.verifyCertificateChainCalled = true;
         }
 
-        public byte[] keyTypes;
-        public byte[][] asn1DerEncodedX500Principals;
-        public boolean clientCertificateRequestedCalled;
+        private byte[] keyTypes;
+        private byte[][] asn1DerEncodedX500Principals;
+        private boolean clientCertificateRequestedCalled;
 
         @Override
         public void clientCertificateRequested(
@@ -763,7 +835,7 @@
             }
         }
 
-        public boolean handshakeCompletedCalled;
+        private boolean handshakeCompletedCalled;
 
         @Override
         public void onSSLStateChange(int type, int val) {
@@ -774,7 +846,7 @@
             this.handshakeCompletedCalled = true;
         }
 
-        public Socket getSocket() {
+        Socket getSocket() {
             return socket;
         }
 
@@ -810,6 +882,7 @@
         private byte[] serverPSKKeyRequestedResultKey;
         private String serverPSKKeyRequestedIdentityHint;
         private String serverPSKKeyRequestedIdentity;
+
         @Override
         public int serverPSKKeyRequested(String identityHint, String identity, byte[] key) {
             if (DEBUG) {
@@ -827,10 +900,35 @@
             }
             return serverPSKKeyRequestedResult;
         }
+
+        private boolean onNewSessionEstablishedInvoked;
+        private boolean onNewSessionEstablishedSaveSession;
+        private long onNewSessionEstablishedSessionNativePointer;
+
+        @Override
+        public void onNewSessionEstablished(long sslSessionNativePtr) {
+            if (DEBUG) {
+                System.out.println("ssl=0x" + Long.toString(sslNativePointer, 16)
+                        + " onNewSessionCreated"
+                        + " ssl=0x" + Long.toString(sslSessionNativePtr, 16));
+            }
+            onNewSessionEstablishedInvoked = true;
+
+            if (onNewSessionEstablishedSaveSession) {
+                NativeCrypto.SSL_SESSION_up_ref(sslSessionNativePtr);
+                onNewSessionEstablishedSessionNativePointer = sslSessionNativePtr;
+            }
+        }
+
+        @Override
+        public long serverSessionRequested(byte[] id) {
+            // TODO(nathanmittler): Implement server-side caching for TLS < 1.3
+            return 0;
+        }
     }
 
-    public static class ClientHooks extends Hooks {
-        protected String pskIdentity;
+    static class ClientHooks extends Hooks {
+        private String pskIdentity;
 
         @Override
         public void configureCallbacks(TestSSLHandshakeCallbacks callbacks) {
@@ -861,20 +959,20 @@
         }
     }
 
-    public static class ServerHooks extends Hooks {
+    static class ServerHooks extends Hooks {
         private final OpenSSLKey privateKey;
         private final long[] certificates;
         private boolean channelIdEnabled;
         private byte[] channelIdAfterHandshake;
         private Throwable channelIdAfterHandshakeException;
 
-        protected String pskIdentityHint;
+        private String pskIdentityHint;
 
         public ServerHooks() {
             this(null, null);
         }
 
-        public ServerHooks(OpenSSLKey privateKey, long[] certificates) {
+        ServerHooks(OpenSSLKey privateKey, long[] certificates) {
             this.privateKey = privateKey;
             this.certificates = certificates;
         }
@@ -935,45 +1033,53 @@
                 executor.submit(new Callable<TestSSLHandshakeCallbacks>() {
                     @Override
                     public TestSSLHandshakeCallbacks call() throws Exception {
-                        @SuppressWarnings("resource")
-                        // Socket needs to remain open after the handshake
-                        Socket socket = (client ? new Socket(listener.getInetAddress(),
-                                                          listener.getLocalPort())
-                                                : listener.accept());
-                        if (timeout == -1) {
-                            return new TestSSLHandshakeCallbacks(socket, 0, null);
-                        }
-                        FileDescriptor fd =
-                                (FileDescriptor) m_Platform_getFileDescriptor.invoke(null, socket);
-                        long c = hooks.getContext();
-                        long s = hooks.beforeHandshake(c);
-                        TestSSLHandshakeCallbacks callback =
-                                new TestSSLHandshakeCallbacks(socket, s, hooks);
-                        hooks.configureCallbacks(callback);
-                        if (DEBUG) {
-                            System.out.println("ssl=0x" + Long.toString(s, 16) + " handshake"
-                                    + " context=0x" + Long.toString(c, 16) + " socket=" + socket
-                                    + " fd=" + fd + " timeout=" + timeout + " client=" + client);
-                        }
-                        long session = NULL;
                         try {
-                            if (client) {
-                                NativeCrypto.SSL_set_connect_state(s);
-                            } else {
-                                NativeCrypto.SSL_set_accept_state(s);
+                            @SuppressWarnings("resource")
+                            // Socket needs to remain open after the handshake
+                            Socket socket = (client ? new Socket(listener.getInetAddress(),
+                                    listener.getLocalPort())
+                                    : listener.accept());
+                            if (timeout == -1) {
+                                return new TestSSLHandshakeCallbacks(socket, 0, null);
                             }
-                            NativeCrypto.SSL_configure_alpn(s, client, alpnProtocols);
-                            NativeCrypto.SSL_do_handshake(s, fd, callback, timeout);
-                            session = NativeCrypto.SSL_get1_session(s);
+                            FileDescriptor fd =
+                                    (FileDescriptor) m_Platform_getFileDescriptor
+                                            .invoke(null, socket);
+                            long c = hooks.getContext();
+                            long s = hooks.beforeHandshake(c);
+                            TestSSLHandshakeCallbacks callback =
+                                    new TestSSLHandshakeCallbacks(socket, s, hooks);
+                            hooks.configureCallbacks(callback);
                             if (DEBUG) {
                                 System.out.println("ssl=0x" + Long.toString(s, 16) + " handshake"
-                                        + " session=0x" + Long.toString(session, 16));
+                                        + " context=0x" + Long.toString(c, 16) + " socket=" + socket
+                                        + " fd=" + fd + " timeout=" + timeout + " client="
+                                        + client);
                             }
-                        } finally {
-                            // Ensure afterHandshake is called to free resources
-                            hooks.afterHandshake(session, s, c, socket, fd, callback);
+                            long session = NULL;
+                            try {
+                                if (client) {
+                                    NativeCrypto.SSL_set_connect_state(s);
+                                } else {
+                                    NativeCrypto.SSL_set_accept_state(s);
+                                }
+                                NativeCrypto.SSL_configure_alpn(s, client, alpnProtocols);
+                                NativeCrypto.SSL_do_handshake(s, fd, callback, timeout);
+                                session = NativeCrypto.SSL_get1_session(s);
+                                if (DEBUG) {
+                                    System.out
+                                            .println("ssl=0x" + Long.toString(s, 16) + " handshake"
+                                                    + " session=0x" + Long.toString(session, 16));
+                                }
+                            } finally {
+                                // Ensure afterHandshake is called to free resources
+                                hooks.afterHandshake(session, s, c, socket, fd, callback);
+                            }
+                            return callback;
+                        } catch (Exception e) {
+                            e.printStackTrace();
+                            throw e;
                         }
-                        return callback;
                     }
                 });
         executor.shutdown();
@@ -985,32 +1091,36 @@
         NativeCrypto.SSL_do_handshake(NULL, null, null, 0);
     }
 
-    @Test
-    public void test_SSL_do_handshake_null_args() throws Exception {
+    @Test(expected = NullPointerException.class)
+    public void test_SSL_do_handshake_withNullFdShouldThrow() throws Exception {
         long c = NativeCrypto.SSL_CTX_new();
         long s = NativeCrypto.SSL_new(c);
         NativeCrypto.SSL_set_connect_state(s);
-
         try {
             NativeCrypto.SSL_do_handshake(s, null, null, 0);
-            fail();
-        } catch (NullPointerException expected) {
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
         }
+    }
 
+    @Test(expected = NullPointerException.class)
+    public void test_SSL_do_handshake_withNullShcShouldThrow() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+        NativeCrypto.SSL_set_connect_state(s);
         try {
             NativeCrypto.SSL_do_handshake(s, INVALID_FD, null, 0);
-            fail();
-        } catch (NullPointerException expected) {
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
         }
-
-        NativeCrypto.SSL_free(s);
-        NativeCrypto.SSL_CTX_free(c);
     }
 
     @Test
     public void test_SSL_do_handshake_normal() throws Exception {
         // normal client and server case
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         Hooks cHooks = new Hooks();
         Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates());
         Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null);
@@ -1027,14 +1137,96 @@
         assertFalse(serverCallback.clientPSKKeyRequestedInvoked);
         assertFalse(clientCallback.serverPSKKeyRequestedInvoked);
         assertFalse(serverCallback.serverPSKKeyRequestedInvoked);
+        assertTrue(clientCallback.onNewSessionEstablishedInvoked);
+        assertTrue(serverCallback.onNewSessionEstablishedInvoked);
         assertTrue(clientCallback.handshakeCompletedCalled);
         assertTrue(serverCallback.handshakeCompletedCalled);
     }
 
     @Test
+    public void test_SSL_do_handshake_reusedSession() throws Exception {
+        // normal client and server case
+        final ServerSocket listener = newServerSocket();
+
+        Future<TestSSLHandshakeCallbacks> client1 =
+                handshake(listener, 0, true, new ClientHooks() {
+                    @Override
+                    public void configureCallbacks(TestSSLHandshakeCallbacks callbacks) {
+                        callbacks.onNewSessionEstablishedSaveSession = true;
+                    }
+                }, null);
+        Future<TestSSLHandshakeCallbacks> server1 = handshake(listener, 0,
+                false, new ServerHooks(getServerPrivateKey(), getServerCertificates()) {
+                    @Override
+                    public void configureCallbacks(TestSSLHandshakeCallbacks callbacks) {
+                        callbacks.onNewSessionEstablishedSaveSession = true;
+                    }
+                }, null);
+        TestSSLHandshakeCallbacks clientCallback1 = client1.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        TestSSLHandshakeCallbacks serverCallback1 = server1.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        assertTrue(clientCallback1.verifyCertificateChainCalled);
+        assertEqualCertificateChains(getServerCertificates(), clientCallback1.certificateChainRefs);
+        assertEquals("ECDHE_RSA", clientCallback1.authMethod);
+        assertFalse(serverCallback1.verifyCertificateChainCalled);
+        assertFalse(clientCallback1.clientCertificateRequestedCalled);
+        assertFalse(serverCallback1.clientCertificateRequestedCalled);
+        assertFalse(clientCallback1.clientPSKKeyRequestedInvoked);
+        assertFalse(serverCallback1.clientPSKKeyRequestedInvoked);
+        assertFalse(clientCallback1.serverPSKKeyRequestedInvoked);
+        assertFalse(serverCallback1.serverPSKKeyRequestedInvoked);
+        assertTrue(clientCallback1.onNewSessionEstablishedInvoked);
+        assertTrue(serverCallback1.onNewSessionEstablishedInvoked);
+        assertTrue(clientCallback1.handshakeCompletedCalled);
+        assertTrue(serverCallback1.handshakeCompletedCalled);
+
+        final long clientSessionContext =
+                clientCallback1.onNewSessionEstablishedSessionNativePointer;
+        final long serverSessionContext =
+                serverCallback1.onNewSessionEstablishedSessionNativePointer;
+
+        Future<TestSSLHandshakeCallbacks> client2 =
+                handshake(listener, 0, true, new ClientHooks() {
+                    @Override
+                    public long beforeHandshake(long c) throws SSLException {
+                        long sslNativePtr = super.beforeHandshake(c);
+                        NativeCrypto.SSL_set_session(sslNativePtr, clientSessionContext);
+                        return sslNativePtr;
+                    }
+                }, null);
+        Future<TestSSLHandshakeCallbacks> server2 = handshake(listener, 0,
+                false, new ServerHooks(getServerPrivateKey(), getServerCertificates()) {
+                    @Override
+                    public long beforeHandshake(long c) throws SSLException {
+                        long sslNativePtr = super.beforeHandshake(c);
+                        NativeCrypto.SSL_set_session(sslNativePtr, serverSessionContext);
+                        return sslNativePtr;
+                    }
+                }, null);
+        TestSSLHandshakeCallbacks clientCallback2 = client2.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        TestSSLHandshakeCallbacks serverCallback2 = server2.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        assertTrue(clientCallback2.verifyCertificateChainCalled);
+        assertEqualCertificateChains(getServerCertificates(), clientCallback2.certificateChainRefs);
+        assertEquals("ECDHE_RSA", clientCallback2.authMethod);
+        assertFalse(serverCallback2.verifyCertificateChainCalled);
+        assertFalse(clientCallback2.clientCertificateRequestedCalled);
+        assertFalse(serverCallback2.clientCertificateRequestedCalled);
+        assertFalse(clientCallback2.clientPSKKeyRequestedInvoked);
+        assertFalse(serverCallback2.clientPSKKeyRequestedInvoked);
+        assertFalse(clientCallback2.serverPSKKeyRequestedInvoked);
+        assertFalse(serverCallback2.serverPSKKeyRequestedInvoked);
+        assertTrue(clientCallback2.onNewSessionEstablishedInvoked);
+        assertTrue(serverCallback2.onNewSessionEstablishedInvoked);
+        assertTrue(clientCallback2.handshakeCompletedCalled);
+        assertTrue(serverCallback2.handshakeCompletedCalled);
+
+        NativeCrypto.SSL_SESSION_free(clientSessionContext);
+        NativeCrypto.SSL_SESSION_free(serverSessionContext);
+    }
+
+    @Test
     public void test_SSL_do_handshake_optional_client_certificate() throws Exception {
         // optional client certificate case
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
 
         Hooks cHooks = new Hooks() {
             @Override
@@ -1066,8 +1258,8 @@
 
         assertTrue(clientCallback.clientCertificateRequestedCalled);
         assertNotNull(clientCallback.keyTypes);
-        assertEquals(new HashSet<String>(Arrays.asList("EC", "RSA")),
-                SSLParametersImpl.getSupportedClientKeyTypes(clientCallback.keyTypes));
+        assertEquals(new HashSet<>(Arrays.asList("EC", "RSA")),
+                SSLUtils.getSupportedClientKeyTypes(clientCallback.keyTypes));
         assertEqualPrincipals(getCaPrincipals(), clientCallback.asn1DerEncodedX500Principals);
         assertFalse(serverCallback.clientCertificateRequestedCalled);
 
@@ -1075,7 +1267,8 @@
         assertFalse(serverCallback.clientPSKKeyRequestedInvoked);
         assertFalse(clientCallback.serverPSKKeyRequestedInvoked);
         assertFalse(serverCallback.serverPSKKeyRequestedInvoked);
-
+        assertTrue(clientCallback.onNewSessionEstablishedInvoked);
+        assertTrue(serverCallback.onNewSessionEstablishedInvoked);
         assertTrue(clientCallback.handshakeCompletedCalled);
         assertTrue(serverCallback.handshakeCompletedCalled);
     }
@@ -1083,7 +1276,7 @@
     @Test
     public void test_SSL_do_handshake_missing_required_certificate() throws Exception {
         // required client certificate negative case
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         try {
             Hooks cHooks = new Hooks();
             Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates()) {
@@ -1091,8 +1284,7 @@
                 public long beforeHandshake(long c) throws SSLException {
                     long s = super.beforeHandshake(c);
                     NativeCrypto.SSL_set_client_CA_list(s, getCaPrincipals());
-                    NativeCrypto.SSL_set_verify(s,
-                            NativeCrypto.SSL_VERIFY_PEER
+                    NativeCrypto.SSL_set_verify(s, NativeCrypto.SSL_VERIFY_PEER
                                     | NativeCrypto.SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
                     return s;
                 }
@@ -1120,7 +1312,7 @@
     @Test
     public void test_SSL_do_handshake_clientCertificateRequested_throws_after_renegotiate()
             throws Exception {
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
 
         Hooks cHooks = new Hooks() {
             @Override
@@ -1154,6 +1346,7 @@
                     NativeCrypto.SSL_write(s, fd, callback, new byte[] {42}, 0, 1,
                             (int) ((TIMEOUT_SECONDS * 1000) / 2));
                 } catch (IOException expected) {
+                    // Ignored.
                 } finally {
                     super.afterHandshake(session, s, c, sock, fd, callback);
                 }
@@ -1174,7 +1367,7 @@
     @Test
     public void test_SSL_do_handshake_client_timeout() throws Exception {
         // client timeout
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         Socket serverSocket = null;
         try {
             Hooks cHooks = new Hooks();
@@ -1198,7 +1391,7 @@
     @Test
     public void test_SSL_do_handshake_server_timeout() throws Exception {
         // server timeout
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         Socket clientSocket = null;
         try {
             Hooks cHooks = new Hooks();
@@ -1221,11 +1414,11 @@
         initChannelIdKey();
 
         // Normal handshake with TLS Channel ID.
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         Hooks cHooks = new Hooks();
         cHooks.channelIdPrivateKey = CHANNEL_ID_PRIVATE_KEY;
         // TLS Channel ID currently requires ECDHE-based key exchanges.
-        cHooks.enabledCipherSuites = Arrays.asList(new String[] {"ECDHE-RSA-AES128-SHA"});
+        cHooks.enabledCipherSuites = Collections.singletonList("ECDHE-RSA-AES128-SHA");
         ServerHooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates());
         sHooks.channelIdEnabled = true;
         sHooks.enabledCipherSuites = cHooks.enabledCipherSuites;
@@ -1243,6 +1436,8 @@
         assertFalse(serverCallback.clientPSKKeyRequestedInvoked);
         assertFalse(clientCallback.serverPSKKeyRequestedInvoked);
         assertFalse(serverCallback.serverPSKKeyRequestedInvoked);
+        assertTrue(clientCallback.onNewSessionEstablishedInvoked);
+        assertTrue(serverCallback.onNewSessionEstablishedInvoked);
         assertTrue(clientCallback.handshakeCompletedCalled);
         assertTrue(serverCallback.handshakeCompletedCalled);
         assertNull(sHooks.channelIdAfterHandshakeException);
@@ -1254,11 +1449,11 @@
         initChannelIdKey();
 
         // Client tries to use TLS Channel ID but the server does not enable/offer the extension.
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         Hooks cHooks = new Hooks();
         cHooks.channelIdPrivateKey = CHANNEL_ID_PRIVATE_KEY;
         // TLS Channel ID currently requires ECDHE-based key exchanges.
-        cHooks.enabledCipherSuites = Arrays.asList(new String[] {"ECDHE-RSA-AES128-SHA"});
+        cHooks.enabledCipherSuites = Collections.singletonList("ECDHE-RSA-AES128-SHA");
         ServerHooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates());
         sHooks.channelIdEnabled = false;
         sHooks.enabledCipherSuites = cHooks.enabledCipherSuites;
@@ -1276,6 +1471,8 @@
         assertFalse(serverCallback.clientPSKKeyRequestedInvoked);
         assertFalse(clientCallback.serverPSKKeyRequestedInvoked);
         assertFalse(serverCallback.serverPSKKeyRequestedInvoked);
+        assertTrue(clientCallback.onNewSessionEstablishedInvoked);
+        assertTrue(serverCallback.onNewSessionEstablishedInvoked);
         assertTrue(clientCallback.handshakeCompletedCalled);
         assertTrue(serverCallback.handshakeCompletedCalled);
         assertNull(sHooks.channelIdAfterHandshakeException);
@@ -1287,11 +1484,11 @@
         initChannelIdKey();
 
         // Client does not use TLS Channel ID when the server has the extension enabled/offered.
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         Hooks cHooks = new Hooks();
         cHooks.channelIdPrivateKey = null;
         // TLS Channel ID currently requires ECDHE-based key exchanges.
-        cHooks.enabledCipherSuites = Arrays.asList(new String[] {"ECDHE-RSA-AES128-SHA"});
+        cHooks.enabledCipherSuites = Collections.singletonList("ECDHE-RSA-AES128-SHA");
         ServerHooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates());
         sHooks.channelIdEnabled = true;
         sHooks.enabledCipherSuites = cHooks.enabledCipherSuites;
@@ -1309,6 +1506,8 @@
         assertFalse(serverCallback.clientPSKKeyRequestedInvoked);
         assertFalse(clientCallback.serverPSKKeyRequestedInvoked);
         assertFalse(serverCallback.serverPSKKeyRequestedInvoked);
+        assertTrue(clientCallback.onNewSessionEstablishedInvoked);
+        assertTrue(serverCallback.onNewSessionEstablishedInvoked);
         assertTrue(clientCallback.handshakeCompletedCalled);
         assertTrue(serverCallback.handshakeCompletedCalled);
         assertNull(sHooks.channelIdAfterHandshakeException);
@@ -1318,7 +1517,7 @@
     @Test
     public void test_SSL_do_handshake_with_psk_normal() throws Exception {
         // normal TLS-PSK client and server case
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         Hooks cHooks = new ClientHooks();
         ServerHooks sHooks = new ServerHooks();
         cHooks.pskEnabled = true;
@@ -1333,6 +1532,8 @@
         assertFalse(serverCallback.verifyCertificateChainCalled);
         assertFalse(clientCallback.clientCertificateRequestedCalled);
         assertFalse(serverCallback.clientCertificateRequestedCalled);
+        assertTrue(clientCallback.onNewSessionEstablishedInvoked);
+        assertTrue(serverCallback.onNewSessionEstablishedInvoked);
         assertTrue(clientCallback.handshakeCompletedCalled);
         assertTrue(serverCallback.handshakeCompletedCalled);
         assertTrue(clientCallback.clientPSKKeyRequestedInvoked);
@@ -1350,7 +1551,7 @@
     public void test_SSL_do_handshake_with_psk_with_identity_and_hint() throws Exception {
         // normal TLS-PSK client and server case where the server provides the client with a PSK
         // identity hint, and the client provides the server with a PSK identity.
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         ClientHooks cHooks = new ClientHooks();
         ServerHooks sHooks = new ServerHooks();
         cHooks.pskEnabled = true;
@@ -1367,6 +1568,8 @@
         assertFalse(serverCallback.verifyCertificateChainCalled);
         assertFalse(clientCallback.clientCertificateRequestedCalled);
         assertFalse(serverCallback.clientCertificateRequestedCalled);
+        assertTrue(clientCallback.onNewSessionEstablishedInvoked);
+        assertTrue(serverCallback.onNewSessionEstablishedInvoked);
         assertTrue(clientCallback.handshakeCompletedCalled);
         assertTrue(serverCallback.handshakeCompletedCalled);
         assertTrue(clientCallback.clientPSKKeyRequestedInvoked);
@@ -1386,7 +1589,7 @@
             throws Exception {
         // normal TLS-PSK client and server case where the server provides the client with a PSK
         // identity hint, and the client provides the server with a PSK identity.
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         ClientHooks cHooks = new ClientHooks();
         ServerHooks sHooks = new ServerHooks();
         cHooks.pskEnabled = true;
@@ -1422,7 +1625,7 @@
 
     @Test
     public void test_SSL_do_handshake_with_psk_key_mismatch() throws Exception {
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         ClientHooks cHooks = new ClientHooks();
         ServerHooks sHooks = new ServerHooks();
         cHooks.pskEnabled = true;
@@ -1442,7 +1645,7 @@
 
     @Test
     public void test_SSL_do_handshake_with_psk_with_no_client_key() throws Exception {
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         ClientHooks cHooks = new ClientHooks();
         ServerHooks sHooks = new ServerHooks();
         cHooks.pskEnabled = true;
@@ -1462,7 +1665,7 @@
 
     @Test
     public void test_SSL_do_handshake_with_psk_with_no_server_key() throws Exception {
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         ClientHooks cHooks = new ClientHooks();
         ServerHooks sHooks = new ServerHooks();
         cHooks.pskEnabled = true;
@@ -1483,7 +1686,7 @@
     @Test
     @SuppressWarnings("deprecation")
     public void test_SSL_do_handshake_with_psk_key_too_long() throws Exception {
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         ClientHooks cHooks = new ClientHooks() {
             @Override
             public void configureCallbacks(TestSSLHandshakeCallbacks callbacks) {
@@ -1511,7 +1714,7 @@
     public void test_SSL_do_handshake_with_ocsp_response() throws Exception {
         final byte[] OCSP_TEST_DATA = new byte[] {1, 2, 3, 4};
 
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         Hooks cHooks = new Hooks() {
             @Override
             public long beforeHandshake(long c) throws SSLException {
@@ -1552,7 +1755,7 @@
         // Each SCT entry has a length (unsigned 16-bit) and data.
         final byte[] SCT_TEST_DATA = new byte[] {0, 6, 0, 4, 1, 2, 3, 4};
 
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         Hooks cHooks = new Hooks() {
             @Override
             public long beforeHandshake(long c) throws SSLException {
@@ -1584,6 +1787,8 @@
         TestSSLHandshakeCallbacks clientCallback = client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
         TestSSLHandshakeCallbacks serverCallback = server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
 
+        assertTrue(clientCallback.onNewSessionEstablishedInvoked);
+        assertTrue(serverCallback.onNewSessionEstablishedInvoked);
         assertTrue(clientCallback.handshakeCompletedCalled);
         assertTrue(serverCallback.handshakeCompletedCalled);
     }
@@ -1607,6 +1812,7 @@
                 NativeCrypto.SSL_use_psk_identity_hint(s, pskIdentityHint.toString());
                 fail();
             } catch (SSLException expected) {
+                // Expected.
             }
         } finally {
             NativeCrypto.SSL_free(s);
@@ -1614,26 +1820,23 @@
         }
     }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_set_session_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_set_session(NULL, NULL);
+    }
+
     @Test
     public void test_SSL_set_session() throws Exception {
-        try {
-            NativeCrypto.SSL_set_session(NULL, NULL);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
-        {
-            long c = NativeCrypto.SSL_CTX_new();
-            long s = NativeCrypto.SSL_new(c);
-            NativeCrypto.SSL_set_session(s, NULL);
-            NativeCrypto.SSL_free(s);
-            NativeCrypto.SSL_CTX_free(c);
-        }
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+        NativeCrypto.SSL_set_session(s, NULL);
+        NativeCrypto.SSL_free(s);
+        NativeCrypto.SSL_CTX_free(c);
 
         {
             final long clientContext = NativeCrypto.SSL_CTX_new();
             final long serverContext = NativeCrypto.SSL_CTX_new();
-            final ServerSocket listener = new ServerSocket(0);
+            final ServerSocket listener = newServerSocket();
             final long[] clientSession = new long[] {NULL};
             final long[] serverSession = new long[] {NULL};
             {
@@ -1714,24 +1917,21 @@
         }
     }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_set_session_creation_enabled_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_set_session_creation_enabled(NULL, false);
+    }
+
     @Test
     public void test_SSL_set_session_creation_enabled() throws Exception {
-        try {
-            NativeCrypto.SSL_set_session_creation_enabled(NULL, false);
-            fail();
-        } catch (NullPointerException expected) {
-        }
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+        NativeCrypto.SSL_set_session_creation_enabled(s, false);
+        NativeCrypto.SSL_set_session_creation_enabled(s, true);
+        NativeCrypto.SSL_free(s);
+        NativeCrypto.SSL_CTX_free(c);
 
-        {
-            long c = NativeCrypto.SSL_CTX_new();
-            long s = NativeCrypto.SSL_new(c);
-            NativeCrypto.SSL_set_session_creation_enabled(s, false);
-            NativeCrypto.SSL_set_session_creation_enabled(s, true);
-            NativeCrypto.SSL_free(s);
-            NativeCrypto.SSL_CTX_free(c);
-        }
-
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
 
         // negative test case for SSL_set_session_creation_enabled(false) on client
         {
@@ -1790,46 +1990,53 @@
         }
     }
 
-    @Test
-    public void test_SSL_set_tlsext_host_name() throws Exception {
-        // NULL SSL
+    @Test(expected = NullPointerException.class)
+    public void SSL_set_tlsext_host_name_withNullSslShouldThrow() throws Exception {
+        NativeCrypto.SSL_set_tlsext_host_name(NULL, null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void SSL_set_tlsext_host_name_withNullHostnameShouldThrow() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+
         try {
-            NativeCrypto.SSL_set_tlsext_host_name(NULL, null);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
-        final String hostname = "www.android.com";
-
-        {
-            long c = NativeCrypto.SSL_CTX_new();
-            long s = NativeCrypto.SSL_new(c);
-
-            // null hostname
-            try {
-                NativeCrypto.SSL_set_tlsext_host_name(s, null);
-                fail();
-            } catch (NullPointerException expected) {
-            }
-
-            // too long hostname
-            try {
-                char[] longHostname = new char[256];
-                Arrays.fill(longHostname, 'w');
-                NativeCrypto.SSL_set_tlsext_host_name(s, new String(longHostname));
-                fail();
-            } catch (SSLException expected) {
-            }
-
-            assertNull(NativeCrypto.SSL_get_servername(s));
-            NativeCrypto.SSL_set_tlsext_host_name(s, new String(hostname));
-            assertEquals(hostname, NativeCrypto.SSL_get_servername(s));
-
+            NativeCrypto.SSL_set_tlsext_host_name(s, null);
+        } finally {
             NativeCrypto.SSL_free(s);
             NativeCrypto.SSL_CTX_free(c);
         }
+    }
 
-        final ServerSocket listener = new ServerSocket(0);
+    @Test(expected = SSLException.class)
+    public void SSL_set_tlsext_host_name_withTooLongHostnameShouldThrow() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+
+        try {
+            char[] longHostname = new char[256];
+            Arrays.fill(longHostname, 'w');
+            NativeCrypto.SSL_set_tlsext_host_name(s, new String(longHostname));
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
+        }
+    }
+
+    @Test
+    public void test_SSL_set_tlsext_host_name() throws Exception {
+        final String hostname = "www.android.com";
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+
+        assertNull(NativeCrypto.SSL_get_servername(s));
+        NativeCrypto.SSL_set_tlsext_host_name(s, hostname);
+        assertEquals(hostname, NativeCrypto.SSL_get_servername(s));
+
+        NativeCrypto.SSL_free(s);
+        NativeCrypto.SSL_CTX_free(c);
+
+        final ServerSocket listener = newServerSocket();
 
         // normal
         Hooks cHooks = new Hooks() {
@@ -1883,7 +2090,7 @@
             }
         };
 
-        ServerSocket listener = new ServerSocket(0);
+        ServerSocket listener = newServerSocket();
         Future<TestSSLHandshakeCallbacks> client =
                 handshake(listener, 0, true, cHooks, clientAlpnProtocols);
         Future<TestSSLHandshakeCallbacks> server =
@@ -1892,15 +2099,13 @@
         server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
     }
 
-    @Test
-    public void test_SSL_get_servername_null() throws Exception {
-        // NULL SSL
-        try {
-            NativeCrypto.SSL_get_servername(NULL);
-            fail();
-        } catch (NullPointerException expected) {
-        }
+    @Test(expected = NullPointerException.class)
+    public void test_SSL_get_servername_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_get_servername(NULL);
+    }
 
+    @Test
+    public void SSL_get_servername_shouldReturnNull() throws Exception {
         long c = NativeCrypto.SSL_CTX_new();
         long s = NativeCrypto.SSL_new(c);
         assertNull(NativeCrypto.SSL_get_servername(s));
@@ -1910,16 +2115,15 @@
         // additional positive testing by test_SSL_set_tlsext_host_name
     }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_renegotiate_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_renegotiate(NULL);
+    }
+
     @Ignore("TODO(nathanmittler): Determine why this doesn't pass on openjdk")
     @Test
-    public void test_SSL_renegotiate() throws Exception {
-        try {
-            NativeCrypto.SSL_renegotiate(NULL);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
-        final ServerSocket listener = new ServerSocket(0);
+    public void test_SSL_renegotiate_fails() throws Exception {
+        final ServerSocket listener = newServerSocket();
         Hooks cHooks = new Hooks() {
             @Override
             public void afterHandshake(long session, long s, long c, Socket sock, FileDescriptor fd,
@@ -1934,7 +2138,17 @@
             @Override
             public void afterHandshake(long session, long s, long c, Socket sock, FileDescriptor fd,
                     SSLHandshakeCallbacks callback) throws Exception {
-                NativeCrypto.SSL_renegotiate(s);
+                try {
+                    // Ensure that BoringSSL throws an exception if you try to manually trigger
+                    // SSL renegotiation, which is what we expect.  The fail() may not directly
+                    // cause the test to fail, because we may be in a separate thread from the main
+                    // test thread, but the SSL_write() call that follows is required for the
+                    // test to pass.
+                    NativeCrypto.SSL_renegotiate(s);
+                    fail("Expected SSLException");
+                } catch (SSLException expected) {
+                    // Expected.
+                }
                 NativeCrypto.SSL_write(s, fd, callback, new byte[] {42}, 0, 1, 0);
                 super.afterHandshake(session, s, c, sock, fd, callback);
             }
@@ -1945,15 +2159,14 @@
         server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
     }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_get_certificate_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_get_certificate(NULL);
+    }
+
     @Test
     public void test_SSL_get_certificate() throws Exception {
-        try {
-            NativeCrypto.SSL_get_certificate(NULL);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
         Hooks cHooks = new Hooks() {
             @Override
             public void afterHandshake(long session, long s, long c, Socket sock, FileDescriptor fd,
@@ -1977,15 +2190,14 @@
         server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
     }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_get_peer_cert_chain_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_get_peer_cert_chain(NULL);
+    }
+
     @Test
     public void test_SSL_get_peer_cert_chain() throws Exception {
-        try {
-            NativeCrypto.SSL_get_peer_cert_chain(NULL);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
 
         Hooks cHooks = new Hooks() {
             @Override
@@ -2006,70 +2218,64 @@
         server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
     }
 
-    final byte[] BYTES = new byte[] {2, -3, 5, 127, 0, -128};
+    private final byte[] BYTES = new byte[] {2, -3, 5, 127, 0, -128};
+
+    @Test(expected = NullPointerException.class)
+    public void SSL_read_withNullSslShouldThrow() throws Exception {
+        NativeCrypto.SSL_read(NULL, null, null, null, 0, 0, 0);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void SSL_read_withNullFdShouldThrow() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+        try {
+            NativeCrypto.SSL_read(s, null, DUMMY_CB, null, 0, 0, 0);
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
+        }
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void SSL_read_withNullCallbacksShouldThrow() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+        try {
+            NativeCrypto.SSL_read(s, INVALID_FD, null, null, 0, 0, 0);
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
+        }
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void SSL_read_withNullBytesShouldThrow() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+        try {
+            NativeCrypto.SSL_read(s, INVALID_FD, DUMMY_CB, null, 0, 0, 0);
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
+        }
+    }
+
+    @Test(expected = SSLException.class)
+    public void SSL_read_beforeHandshakeShouldThrow() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+        try {
+            NativeCrypto.SSL_read(s, INVALID_FD, DUMMY_CB, new byte[1], 0, 1, 0);
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
+        }
+    }
 
     @Test
     public void test_SSL_read() throws Exception {
-        // NULL ssl
-        try {
-            NativeCrypto.SSL_read(NULL, null, null, null, 0, 0, 0);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
-        // null FileDescriptor
-        {
-            long c = NativeCrypto.SSL_CTX_new();
-            long s = NativeCrypto.SSL_new(c);
-            try {
-                NativeCrypto.SSL_read(s, null, DUMMY_CB, null, 0, 0, 0);
-                fail();
-            } catch (NullPointerException expected) {
-            }
-            NativeCrypto.SSL_free(s);
-            NativeCrypto.SSL_CTX_free(c);
-        }
-
-        // null SSLHandshakeCallbacks
-        {
-            long c = NativeCrypto.SSL_CTX_new();
-            long s = NativeCrypto.SSL_new(c);
-            try {
-                NativeCrypto.SSL_read(s, INVALID_FD, null, null, 0, 0, 0);
-                fail();
-            } catch (NullPointerException expected) {
-            }
-            NativeCrypto.SSL_free(s);
-            NativeCrypto.SSL_CTX_free(c);
-        }
-
-        // null byte array
-        {
-            long c = NativeCrypto.SSL_CTX_new();
-            long s = NativeCrypto.SSL_new(c);
-            try {
-                NativeCrypto.SSL_read(s, INVALID_FD, DUMMY_CB, null, 0, 0, 0);
-                fail();
-            } catch (NullPointerException expected) {
-            }
-            NativeCrypto.SSL_free(s);
-            NativeCrypto.SSL_CTX_free(c);
-        }
-
-        // handshaking not yet performed
-        {
-            long c = NativeCrypto.SSL_CTX_new();
-            long s = NativeCrypto.SSL_new(c);
-            try {
-                NativeCrypto.SSL_read(s, INVALID_FD, DUMMY_CB, new byte[1], 0, 1, 0);
-                fail();
-            } catch (SSLException expected) {
-            }
-            NativeCrypto.SSL_free(s);
-            NativeCrypto.SSL_CTX_free(c);
-        }
-
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
 
         // normal case
         {
@@ -2128,84 +2334,78 @@
         }
     }
 
-    @Test
-    public void test_SSL_write() throws Exception {
+    @Test(expected = NullPointerException.class)
+    public void SSL_write_withNullSslShouldThrow() throws Exception {
+        NativeCrypto.SSL_write(NULL, null, null, null, 0, 0, 0);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void SSL_write_withNullFdShouldThrow() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
         try {
-            NativeCrypto.SSL_write(NULL, null, null, null, 0, 0, 0);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
-        // null FileDescriptor
-        {
-            long c = NativeCrypto.SSL_CTX_new();
-            long s = NativeCrypto.SSL_new(c);
-            try {
-                NativeCrypto.SSL_write(s, null, DUMMY_CB, null, 0, 1, 0);
-                fail();
-            } catch (NullPointerException expected) {
-            }
+            NativeCrypto.SSL_write(s, null, DUMMY_CB, null, 0, 1, 0);
+        } finally {
             NativeCrypto.SSL_free(s);
             NativeCrypto.SSL_CTX_free(c);
         }
+    }
 
-        // null SSLHandshakeCallbacks
-        {
-            long c = NativeCrypto.SSL_CTX_new();
-            long s = NativeCrypto.SSL_new(c);
-            try {
-                NativeCrypto.SSL_write(s, INVALID_FD, null, null, 0, 1, 0);
-                fail();
-            } catch (NullPointerException expected) {
-            }
+    @Test(expected = NullPointerException.class)
+    public void SSL_write_withNullCallbacksShouldThrow() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+        try {
+            NativeCrypto.SSL_write(s, INVALID_FD, null, null, 0, 1, 0);
+        } finally {
             NativeCrypto.SSL_free(s);
             NativeCrypto.SSL_CTX_free(c);
         }
+    }
 
-        // null byte array
-        {
-            long c = NativeCrypto.SSL_CTX_new();
-            long s = NativeCrypto.SSL_new(c);
-            try {
-                NativeCrypto.SSL_write(s, INVALID_FD, DUMMY_CB, null, 0, 1, 0);
-                fail();
-            } catch (NullPointerException expected) {
-            }
+    @Test(expected = NullPointerException.class)
+    public void SSL_write_withNullBytesShouldThrow() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+        try {
+            NativeCrypto.SSL_write(s, INVALID_FD, DUMMY_CB, null, 0, 1, 0);
+        } finally {
             NativeCrypto.SSL_free(s);
             NativeCrypto.SSL_CTX_free(c);
         }
+    }
 
-        // handshaking not yet performed
-        {
-            long c = NativeCrypto.SSL_CTX_new();
-            long s = NativeCrypto.SSL_new(c);
-            try {
-                NativeCrypto.SSL_write(s, INVALID_FD, DUMMY_CB, new byte[1], 0, 1, 0);
-                fail();
-            } catch (SSLException expected) {
-            }
+    @Test(expected = SSLException.class)
+    public void SSL_write_beforeHandshakeShouldThrow() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+        try {
+            NativeCrypto.SSL_write(s, INVALID_FD, DUMMY_CB, new byte[1], 0, 1, 0);
+        } finally {
             NativeCrypto.SSL_free(s);
             NativeCrypto.SSL_CTX_free(c);
         }
+    }
 
-        // positively tested by test_SSL_read
+    @Test
+    public void SSL_interrupt_withNullShouldSucceed() {
+        // SSL_interrupt is a rare case that tolerates a null SSL argument
+        NativeCrypto.SSL_interrupt(NULL);
+    }
+
+    @Test
+    public void SSL_interrupt_withoutHandshakeShouldSucceed() throws Exception {
+        // also works without handshaking
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+        NativeCrypto.SSL_interrupt(s);
+        NativeCrypto.SSL_free(s);
+        NativeCrypto.SSL_CTX_free(c);
     }
 
     @Test
     public void test_SSL_interrupt() throws Exception {
-        // SSL_interrupt is a rare case that tolerates a null SSL argument
-        NativeCrypto.SSL_interrupt(NULL);
-
-        // also works without handshaking
-        {
-            long c = NativeCrypto.SSL_CTX_new();
-            long s = NativeCrypto.SSL_new(c);
-            NativeCrypto.SSL_interrupt(s);
-            NativeCrypto.SSL_free(s);
-            NativeCrypto.SSL_CTX_free(c);
-        }
-
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
 
         Hooks cHooks = new Hooks() {
             @Override
@@ -2223,9 +2423,10 @@
                     @Override
                     public void run() {
                         try {
-                            Thread.sleep(1 * 1000);
+                            Thread.sleep(1000);
                             NativeCrypto.SSL_interrupt(s);
                         } catch (Exception e) {
+                            // Expected.
                         }
                     }
                 }.start();
@@ -2255,7 +2456,7 @@
     }
 
     @Test
-    public void test_SSL_shutdown() throws Exception {
+    public void SSL_shutdown_withNullFdShouldSucceed() throws Exception {
         // We tolerate a null FileDescriptor
         wrapWithSSLSession(new SSLSessionWrappedTask() {
             @Override
@@ -2263,32 +2464,31 @@
                 NativeCrypto.SSL_shutdown(sslSession, null, DUMMY_CB);
             }
         });
+    }
 
-        // null SSLHandshakeCallbacks
+    @Test(expected = NullPointerException.class)
+    public void SSL_shutdown_withNullCallbacksShouldThrow() throws Exception {
         wrapWithSSLSession(new SSLSessionWrappedTask() {
             @Override
             public void run(long sslSession) throws Exception {
-                try {
-                    NativeCrypto.SSL_shutdown(sslSession, INVALID_FD, null);
-                    fail();
-                } catch (NullPointerException expected) {
-                    // Ignored.
-                }
+                NativeCrypto.SSL_shutdown(sslSession, INVALID_FD, null);
             }
         });
+    }
 
+    @Test
+    public void SSL_shutdown_withNullSslShouldSucceed() throws Exception {
         // SSL_shutdown is a rare case that tolerates a null SSL argument
         NativeCrypto.SSL_shutdown(NULL, INVALID_FD, DUMMY_CB);
+    }
 
+    @Test(expected = SocketException.class)
+    public void SSL_shutdown_beforeHandshakeShouldThrow() throws Exception {
         // handshaking not yet performed
         wrapWithSSLSession(new SSLSessionWrappedTask() {
             @Override
             public void run(long sslSession) throws Exception {
-                try {
-                    NativeCrypto.SSL_shutdown(sslSession, INVALID_FD, DUMMY_CB);
-                    fail();
-                } catch (SocketException expected) {
-                }
+                NativeCrypto.SSL_shutdown(sslSession, INVALID_FD, DUMMY_CB);
             }
         });
 
@@ -2296,14 +2496,13 @@
         // SSL_shutdown to ensure SSL_SESSIONs are reused.
     }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_free_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_free(NULL);
+    }
+
     @Test
     public void test_SSL_free() throws Exception {
-        try {
-            NativeCrypto.SSL_free(NULL);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
         long c = NativeCrypto.SSL_CTX_new();
         NativeCrypto.SSL_free(NativeCrypto.SSL_new(c));
         NativeCrypto.SSL_CTX_free(c);
@@ -2312,15 +2511,14 @@
         // uses use SSL_free to cleanup in afterHandshake.
     }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_SESSION_session_id_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_SESSION_session_id(NULL);
+    }
+
     @Test
     public void test_SSL_SESSION_session_id() throws Exception {
-        try {
-            NativeCrypto.SSL_SESSION_session_id(NULL);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
 
         Hooks cHooks = new Hooks() {
             @Override
@@ -2339,15 +2537,14 @@
         server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
     }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_SESSION_get_time_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_SESSION_get_time(NULL);
+    }
+
     @Test
     public void test_SSL_SESSION_get_time() throws Exception {
-        try {
-            NativeCrypto.SSL_SESSION_get_time(NULL);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
 
         {
             Hooks cHooks = new Hooks() {
@@ -2368,15 +2565,14 @@
         }
     }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_SESSION_get_version_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_SESSION_get_version(NULL);
+    }
+
     @Test
     public void test_SSL_SESSION_get_version() throws Exception {
-        try {
-            NativeCrypto.SSL_SESSION_get_version(NULL);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
 
         Hooks cHooks = new Hooks() {
             @Override
@@ -2394,15 +2590,14 @@
         server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
     }
 
+    @Test(expected = NullPointerException.class)
+    public void SSL_SESSION_cipher_withNullShouldThrow() throws Exception {
+        NativeCrypto.SSL_SESSION_cipher(NULL);
+    }
+
     @Test
     public void test_SSL_SESSION_cipher() throws Exception {
-        try {
-            NativeCrypto.SSL_SESSION_cipher(NULL);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
 
         Hooks cHooks = new Hooks() {
             @Override
@@ -2429,15 +2624,14 @@
         NativeCrypto.SSL_SESSION_free(NULL);
     }
 
+    @Test(expected = NullPointerException.class)
+    public void i2d_SSL_Session_WithNullSessionShouldThrow() throws Exception {
+        NativeCrypto.i2d_SSL_SESSION(NULL);
+    }
+
     @Test
     public void test_i2d_SSL_SESSION() throws Exception {
-        try {
-            NativeCrypto.i2d_SSL_SESSION(NULL);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-
-        final ServerSocket listener = new ServerSocket(0);
+        final ServerSocket listener = newServerSocket();
 
         Hooks cHooks = new Hooks() {
             @Override
@@ -2495,8 +2689,8 @@
         NativeCrypto.RAND_bytes(output);
 
         boolean isZero = true;
-        for (int i = 0; i < output.length; i++) {
-            isZero &= (output[i] == 0);
+        for (byte anOutput : output) {
+            isZero &= (anOutput == 0);
         }
 
         assertFalse("Random output was zero. This is a very low probability event (1 in 2^128) "
@@ -2504,14 +2698,9 @@
                 isZero);
     }
 
-    @Test
-    public void test_RAND_bytes_Null_Failure() throws Exception {
-        byte[] output = null;
-        try {
-            NativeCrypto.RAND_bytes(output);
-            fail("Should be an error on null buffer input");
-        } catch (RuntimeException expected) {
-        }
+    @Test(expected = RuntimeException.class)
+    public void RAND_bytes_withNullShouldThrow() throws Exception {
+        NativeCrypto.RAND_bytes(null);
     }
 
     @Test(expected = NullPointerException.class)
@@ -2519,16 +2708,19 @@
         NativeCrypto.EVP_get_digestbyname(null);
     }
 
+    @Test(expected = RuntimeException.class)
+    public void EVP_get_digestbyname_withEmptyShouldThrow() throws Exception {
+        NativeCrypto.EVP_get_digestbyname("");
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void EVP_get_digestbyname_withInvalidDigestShouldThrow() throws Exception {
+        NativeCrypto.EVP_get_digestbyname("foobar");
+    }
+
     @Test
     public void test_EVP_get_digestbyname() throws Exception {
         assertTrue(NativeCrypto.EVP_get_digestbyname("sha256") != NULL);
-
-        try {
-            NativeCrypto.EVP_get_digestbyname("");
-            NativeCrypto.EVP_get_digestbyname("foobar");
-            fail();
-        } catch (RuntimeException expected) {
-        }
     }
 
     @Test
@@ -2556,12 +2748,14 @@
             NativeCrypto.EVP_DigestSignInit(ctx, 0, pkey);
             fail();
         } catch (RuntimeException expected) {
+            // Expected.
         }
 
         try {
             NativeCrypto.EVP_DigestSignInit(ctx, evpMd, null);
             fail();
         } catch (RuntimeException expected) {
+            // Expected.
         }
     }
 
@@ -2570,18 +2764,14 @@
         NativeCrypto.get_RSA_private_params(null);
     }
 
-    @Test
+    @Test(expected = RuntimeException.class)
     public void test_get_RSA_private_params() throws Exception {
         // Test getting params for the wrong kind of key.
         final long groupCtx = NativeCrypto.EC_GROUP_new_by_curve_name("prime256v1");
         assertFalse(groupCtx == NULL);
         NativeRef.EC_GROUP group = new NativeRef.EC_GROUP(groupCtx);
         NativeRef.EVP_PKEY ctx = new NativeRef.EVP_PKEY(NativeCrypto.EC_KEY_generate_key(group));
-        try {
-            NativeCrypto.get_RSA_private_params(ctx);
-            fail();
-        } catch (RuntimeException expected) {
-        }
+        NativeCrypto.get_RSA_private_params(ctx);
     }
 
     @Test(expected = NullPointerException.class)
@@ -2589,18 +2779,14 @@
         NativeCrypto.get_RSA_public_params(null);
     }
 
-    @Test
+    @Test(expected = RuntimeException.class)
     public void test_get_RSA_public_params() throws Exception {
         // Test getting params for the wrong kind of key.
         final long groupCtx = NativeCrypto.EC_GROUP_new_by_curve_name("prime256v1");
         assertFalse(groupCtx == NULL);
         NativeRef.EC_GROUP group = new NativeRef.EC_GROUP(groupCtx);
         NativeRef.EVP_PKEY ctx = new NativeRef.EVP_PKEY(NativeCrypto.EC_KEY_generate_key(group));
-        try {
-            NativeCrypto.get_RSA_public_params(ctx);
-            fail();
-        } catch (RuntimeException expected) {
-        }
+        NativeCrypto.get_RSA_public_params(ctx);
     }
 
     @Test(expected = NullPointerException.class)
@@ -2655,8 +2841,6 @@
         long groupRef = NativeCrypto.EC_GROUP_new_by_curve_name(name);
         assertFalse(groupRef == NULL);
         NativeRef.EC_GROUP group = new NativeRef.EC_GROUP(groupRef);
-        assertEquals(NativeCrypto.OBJ_txt2nid_longName(name),
-                NativeCrypto.EC_GROUP_get_curve_name(group));
 
         // prime
         BigInteger p = new BigInteger(pStr, 16);
@@ -2745,6 +2929,7 @@
             NativeCrypto.ECDH_compute_key(out, outOffset, null, pkey2Ref);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
 
         // Assert that it fails when only the second key is null
@@ -2752,21 +2937,22 @@
             NativeCrypto.ECDH_compute_key(out, outOffset, pkey1Ref, null);
             fail();
         } catch (NullPointerException expected) {
+            // Expected.
         }
     }
 
+    @Test(expected = NullPointerException.class)
+    public void EVP_CipherInit_ex_withNullCtxShouldThrow() throws Exception {
+        final long evpCipher = NativeCrypto.EVP_get_cipherbyname("aes-128-ecb");
+        NativeCrypto.EVP_CipherInit_ex(null, evpCipher, null, null, true);
+    }
+
     @Test
     public void test_EVP_CipherInit_ex_Null_Failure() throws Exception {
         final NativeRef.EVP_CIPHER_CTX ctx =
                 new NativeRef.EVP_CIPHER_CTX(NativeCrypto.EVP_CIPHER_CTX_new());
         final long evpCipher = NativeCrypto.EVP_get_cipherbyname("aes-128-ecb");
 
-        try {
-            NativeCrypto.EVP_CipherInit_ex(null, evpCipher, null, null, true);
-            fail("Null context should throw NullPointerException");
-        } catch (NullPointerException expected) {
-        }
-
         /* Initialize encrypting. */
         NativeCrypto.EVP_CipherInit_ex(ctx, evpCipher, null, null, true);
         NativeCrypto.EVP_CipherInit_ex(ctx, NULL, null, null, true);
@@ -2937,7 +3123,7 @@
     @Test(expected = NullPointerException.class)
     public void EVP_PKEY_CTX_set_rsa_oaep_md_NullMdCtx() throws Exception {
         long pkeyCtx = getRawPkeyCtxForEncrypt();
-        NativeRef.EVP_PKEY_CTX holder = new NativeRef.EVP_PKEY_CTX(pkeyCtx);
+        new NativeRef.EVP_PKEY_CTX(pkeyCtx);
         NativeCrypto.EVP_PKEY_CTX_set_rsa_oaep_md(pkeyCtx, NULL);
     }
 
@@ -2955,4 +3141,8 @@
         }
         fail("\"" + actualValue + "\" does not contain \"" + expectedSubstring + "\"");
     }
+
+    private static ServerSocket newServerSocket() throws IOException {
+        return new ServerSocket(0, 50, InetAddress.getLoopbackAddress());
+    }
 }
diff --git a/openjdk/src/test/java/org/conscrypt/NativeRefTest.java b/openjdk/src/test/java/org/conscrypt/NativeRefTest.java
index ed84813..e13297b 100644
--- a/openjdk/src/test/java/org/conscrypt/NativeRefTest.java
+++ b/openjdk/src/test/java/org/conscrypt/NativeRefTest.java
@@ -21,7 +21,11 @@
 public class NativeRefTest extends TestCase {
     public void test_zeroContextThrowsNullPointException() {
         try {
-            new NativeRef(0) {};
+            new NativeRef(0) {
+                @Override
+                void doFree(long context) {
+                }
+            };
             fail("Should throw NullPointerException when arguments are NULL");
         } catch (NullPointerException expected) {
         }
diff --git a/openjdk/src/test/java/org/conscrypt/OpenSSLExtendedSessionImplTest.java b/openjdk/src/test/java/org/conscrypt/OpenSSLExtendedSessionImplTest.java
deleted file mode 100644
index f3414a9..0000000
--- a/openjdk/src/test/java/org/conscrypt/OpenSSLExtendedSessionImplTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli$
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.conscrypt;
-
-import java.util.List;
-import javax.net.ssl.ExtendedSSLSession;
-import javax.net.ssl.SNIHostName;
-import javax.net.ssl.SNIServerName;
-import junit.framework.TestCase;
-
-/**
- * Test for OpenSSLExtendedSessionImpl
- */
-public class OpenSSLExtendedSessionImplTest extends TestCase {
-  static class MockSSLSession extends OpenSSLSessionImpl {
-    MockSSLSession() {
-      super(0, null, null, null, null, null, 0, null);
-    }
-
-    @Override
-    public String getRequestedServerName() {
-      return "server.name";
-    }
-  }
-
-  public void test_getRequestedServerNames() {
-    AbstractOpenSSLSession session = new MockSSLSession();
-    ExtendedSSLSession extendedSession = new OpenSSLExtendedSessionImpl(session);
-    List<SNIServerName> names = extendedSession.getRequestedServerNames();
-    assertEquals("server.name", ((SNIHostName) names.get(0)).getAsciiName());
-  }
-}
diff --git a/openjdk/src/test/java/org/conscrypt/SSLParametersImplTest.java b/openjdk/src/test/java/org/conscrypt/SSLParametersImplTest.java
deleted file mode 100644
index 47837c7..0000000
--- a/openjdk/src/test/java/org/conscrypt/SSLParametersImplTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2013 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 java.util.Arrays;
-import java.util.HashSet;
-import junit.framework.TestCase;
-
-public class SSLParametersImplTest extends TestCase {
-
-  public void testGetClientKeyType() throws Exception {
-    // See http://www.ietf.org/assignments/tls-parameters/tls-parameters.xml
-    byte b = Byte.MIN_VALUE;
-    do {
-      String byteString = Byte.toString(b);
-      String keyType = SSLParametersImpl.getClientKeyType(b);
-      switch (b) {
-        case 1:
-          assertEquals(byteString, "RSA", keyType);
-          break;
-        case 3:
-          assertEquals(byteString, "DH_RSA", keyType);
-          break;
-        case 64:
-          assertEquals(byteString, "EC", keyType);
-          break;
-        case 65:
-          assertEquals(byteString, "EC_RSA", keyType);
-          break;
-        case 66:
-          assertEquals(byteString, "EC_EC", keyType);
-          break;
-        default:
-          assertNull(byteString, keyType);
-      }
-      b++;
-    } while (b != Byte.MIN_VALUE);
-  }
-
-  public void testGetSupportedClientKeyTypes() throws Exception {
-      // Create an array with all possible values. Also, duplicate all values.
-      byte[] allClientCertificateTypes = new byte[512];
-      for (int i = 0; i < allClientCertificateTypes.length; i++) {
-          allClientCertificateTypes[i] = (byte) i;
-      }
-      assertEquals(
-              new HashSet<String>(Arrays.asList("RSA", "DH_RSA", "EC", "EC_RSA", "EC_EC")),
-              SSLParametersImpl.getSupportedClientKeyTypes(allClientCertificateTypes));
-  }
-}
diff --git a/openjdk/src/test/java/org/conscrypt/SSLUtilsTest.java b/openjdk/src/test/java/org/conscrypt/SSLUtilsTest.java
index c7f4d9d..73fcfb3 100644
--- a/openjdk/src/test/java/org/conscrypt/SSLUtilsTest.java
+++ b/openjdk/src/test/java/org/conscrypt/SSLUtilsTest.java
@@ -18,7 +18,11 @@
 
 import static org.conscrypt.TestUtils.UTF_8;
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
+import java.util.Arrays;
+import java.util.HashSet;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -62,6 +66,48 @@
         assertArrayEquals(expected, actual);
     }
 
+    @Test
+    public void testGetClientKeyType() throws Exception {
+        // See http://www.ietf.org/assignments/tls-parameters/tls-parameters.xml
+        byte b = Byte.MIN_VALUE;
+        do {
+            String byteString = Byte.toString(b);
+            String keyType = SSLUtils.getClientKeyType(b);
+            switch (b) {
+                case 1:
+                    assertEquals(byteString, "RSA", keyType);
+                    break;
+                case 3:
+                    assertEquals(byteString, "DH_RSA", keyType);
+                    break;
+                case 64:
+                    assertEquals(byteString, "EC", keyType);
+                    break;
+                case 65:
+                    assertEquals(byteString, "EC_RSA", keyType);
+                    break;
+                case 66:
+                    assertEquals(byteString, "EC_EC", keyType);
+                    break;
+                default:
+                    assertNull(byteString, keyType);
+            }
+            b++;
+        } while (b != Byte.MIN_VALUE);
+    }
+
+    @Test
+    public void testGetSupportedClientKeyTypes() throws Exception {
+        // Create an array with all possible values. Also, duplicate all values.
+        byte[] allClientCertificateTypes = new byte[512];
+        for (int i = 0; i < allClientCertificateTypes.length; i++) {
+            allClientCertificateTypes[i] = (byte) i;
+        }
+        assertEquals(
+                new HashSet<String>(Arrays.asList("RSA", "DH_RSA", "EC", "EC_RSA", "EC_EC")),
+                SSLUtils.getSupportedClientKeyTypes(allClientCertificateTypes));
+    }
+
     private static String[] toStrings(byte[][] protocols) {
         int numProtocols = protocols.length;
         String[] out = new String[numProtocols];
diff --git a/openjdk/src/test/java/org/conscrypt/ServerSessionContextTest.java b/openjdk/src/test/java/org/conscrypt/ServerSessionContextTest.java
new file mode 100644
index 0000000..7d1a8ce
--- /dev/null
+++ b/openjdk/src/test/java/org/conscrypt/ServerSessionContextTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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 java.util.Enumeration;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ServerSessionContextTest extends AbstractSessionContextTest<ServerSessionContext> {
+
+    @Override
+    ServerSessionContext newContext() {
+        return new ServerSessionContext();
+    }
+
+    @Override
+    SslSessionWrapper getCachedSession(ServerSessionContext context, SslSessionWrapper s) {
+        return context.getSessionFromCache(s.getId());
+    }
+
+    @Override
+    int size(ServerSessionContext context) {
+        int count = 0;
+        Enumeration<byte[]> ids = context.getIds();
+        while (ids.hasMoreElements()) {
+            ids.nextElement();
+            count++;
+        }
+        return count;
+    }
+}
diff --git a/openjdk/src/test/java/org/conscrypt/SslSessionWrapperTest.java b/openjdk/src/test/java/org/conscrypt/SslSessionWrapperTest.java
new file mode 100644
index 0000000..dc600d4
--- /dev/null
+++ b/openjdk/src/test/java/org/conscrypt/SslSessionWrapperTest.java
@@ -0,0 +1,629 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License", "www.google.com", 443);
+ * 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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SslSessionWrapperTest {
+    /*
+     * Taken from external/boringssl/src/ssl/ssl_test.cc: kOpenSSLSession is a
+     * serialized SSL_SESSION.
+     */
+    private static final byte[] kOpenSSLSession = new byte[] {(byte) 0x30, (byte) 0x82, (byte) 0x05,
+            (byte) 0xAA, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x02, (byte) 0x02,
+            (byte) 0x03, (byte) 0x03, (byte) 0x04, (byte) 0x02, (byte) 0xC0, (byte) 0x2F,
+            (byte) 0x04, (byte) 0x20, (byte) 0x06, (byte) 0xE5, (byte) 0x0D, (byte) 0x67,
+            (byte) 0x76, (byte) 0xAE, (byte) 0x18, (byte) 0x7E, (byte) 0x66, (byte) 0xDE,
+            (byte) 0xA3, (byte) 0x5C, (byte) 0xF0, (byte) 0x2E, (byte) 0x43, (byte) 0x51,
+            (byte) 0x2A, (byte) 0x60, (byte) 0x97, (byte) 0x19, (byte) 0xD3, (byte) 0x60,
+            (byte) 0x5A, (byte) 0xF1, (byte) 0x93, (byte) 0xDD, (byte) 0xCB, (byte) 0x24,
+            (byte) 0x57, (byte) 0x4C, (byte) 0x90, (byte) 0x90, (byte) 0x04, (byte) 0x30,
+            (byte) 0x26, (byte) 0x5A, (byte) 0xE5, (byte) 0xCE, (byte) 0x40, (byte) 0x16,
+            (byte) 0x04, (byte) 0xE5, (byte) 0xA2, (byte) 0x2E, (byte) 0x3F, (byte) 0xE3,
+            (byte) 0x27, (byte) 0xBE, (byte) 0x83, (byte) 0xEE, (byte) 0x5F, (byte) 0x94,
+            (byte) 0x5E, (byte) 0x88, (byte) 0xB3, (byte) 0x3F, (byte) 0x62, (byte) 0x88,
+            (byte) 0xD8, (byte) 0x2E, (byte) 0xC8, (byte) 0xD8, (byte) 0x57, (byte) 0x1C,
+            (byte) 0xA8, (byte) 0xC9, (byte) 0x88, (byte) 0x7C, (byte) 0x59, (byte) 0xA6,
+            (byte) 0x91, (byte) 0x4C, (byte) 0xB7, (byte) 0xDA, (byte) 0x72, (byte) 0x09,
+            (byte) 0xD2, (byte) 0x66, (byte) 0x47, (byte) 0x21, (byte) 0x6A, (byte) 0x09,
+            (byte) 0xA1, (byte) 0x06, (byte) 0x02, (byte) 0x04, (byte) 0x54, (byte) 0x43,
+            (byte) 0x3B, (byte) 0x8E, (byte) 0xA2, (byte) 0x04, (byte) 0x02, (byte) 0x02,
+            (byte) 0x01, (byte) 0x2C, (byte) 0xA3, (byte) 0x82, (byte) 0x04, (byte) 0x7A,
+            (byte) 0x30, (byte) 0x82, (byte) 0x04, (byte) 0x76, (byte) 0x30, (byte) 0x82,
+            (byte) 0x03, (byte) 0x5E, (byte) 0xA0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
+            (byte) 0x02, (byte) 0x02, (byte) 0x08, (byte) 0x2B, (byte) 0xD7, (byte) 0x54,
+            (byte) 0xBE, (byte) 0xC3, (byte) 0xD6, (byte) 0x4A, (byte) 0x55, (byte) 0x30,
+            (byte) 0x0D, (byte) 0x06, (byte) 0x09, (byte) 0x2A, (byte) 0x86, (byte) 0x48,
+            (byte) 0x86, (byte) 0xF7, (byte) 0x0D, (byte) 0x01, (byte) 0x01, (byte) 0x05,
+            (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x49, (byte) 0x31, (byte) 0x0B,
+            (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
+            (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31,
+            (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x04, (byte) 0x0A, (byte) 0x13, (byte) 0x0A, (byte) 0x47, (byte) 0x6F,
+            (byte) 0x6F, (byte) 0x67, (byte) 0x6C, (byte) 0x65, (byte) 0x20, (byte) 0x49,
+            (byte) 0x6E, (byte) 0x63, (byte) 0x31, (byte) 0x25, (byte) 0x30, (byte) 0x23,
+            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x13,
+            (byte) 0x1C, (byte) 0x47, (byte) 0x6F, (byte) 0x6F, (byte) 0x67, (byte) 0x6C,
+            (byte) 0x65, (byte) 0x20, (byte) 0x49, (byte) 0x6E, (byte) 0x74, (byte) 0x65,
+            (byte) 0x72, (byte) 0x6E, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x41,
+            (byte) 0x75, (byte) 0x74, (byte) 0x68, (byte) 0x6F, (byte) 0x72, (byte) 0x69,
+            (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x47, (byte) 0x32, (byte) 0x30,
+            (byte) 0x1E, (byte) 0x17, (byte) 0x0D, (byte) 0x31, (byte) 0x34, (byte) 0x31,
+            (byte) 0x30, (byte) 0x30, (byte) 0x38, (byte) 0x31, (byte) 0x32, (byte) 0x30,
+            (byte) 0x37, (byte) 0x35, (byte) 0x37, (byte) 0x5A, (byte) 0x17, (byte) 0x0D,
+            (byte) 0x31, (byte) 0x35, (byte) 0x30, (byte) 0x31, (byte) 0x30, (byte) 0x36,
+            (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30,
+            (byte) 0x5A, (byte) 0x30, (byte) 0x68, (byte) 0x31, (byte) 0x0B, (byte) 0x30,
+            (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
+            (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x13,
+            (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
+            (byte) 0x08, (byte) 0x0C, (byte) 0x0A, (byte) 0x43, (byte) 0x61, (byte) 0x6C,
+            (byte) 0x69, (byte) 0x66, (byte) 0x6F, (byte) 0x72, (byte) 0x6E, (byte) 0x69,
+            (byte) 0x61, (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06,
+            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x0C, (byte) 0x0D,
+            (byte) 0x4D, (byte) 0x6F, (byte) 0x75, (byte) 0x6E, (byte) 0x74, (byte) 0x61,
+            (byte) 0x69, (byte) 0x6E, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65,
+            (byte) 0x77, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06,
+            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0A, (byte) 0x0C, (byte) 0x0A,
+            (byte) 0x47, (byte) 0x6F, (byte) 0x6F, (byte) 0x67, (byte) 0x6C, (byte) 0x65,
+            (byte) 0x20, (byte) 0x49, (byte) 0x6E, (byte) 0x63, (byte) 0x31, (byte) 0x17,
+            (byte) 0x30, (byte) 0x15, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
+            (byte) 0x03, (byte) 0x0C, (byte) 0x0E, (byte) 0x77, (byte) 0x77, (byte) 0x77,
+            (byte) 0x2E, (byte) 0x67, (byte) 0x6F, (byte) 0x6F, (byte) 0x67, (byte) 0x6C,
+            (byte) 0x65, (byte) 0x2E, (byte) 0x63, (byte) 0x6F, (byte) 0x6D, (byte) 0x30,
+            (byte) 0x82, (byte) 0x01, (byte) 0x22, (byte) 0x30, (byte) 0x0D, (byte) 0x06,
+            (byte) 0x09, (byte) 0x2A, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xF7,
+            (byte) 0x0D, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00,
+            (byte) 0x03, (byte) 0x82, (byte) 0x01, (byte) 0x0F, (byte) 0x00, (byte) 0x30,
+            (byte) 0x82, (byte) 0x01, (byte) 0x0A, (byte) 0x02, (byte) 0x82, (byte) 0x01,
+            (byte) 0x01, (byte) 0x00, (byte) 0x9C, (byte) 0x29, (byte) 0xE2, (byte) 0xEB,
+            (byte) 0xA6, (byte) 0x50, (byte) 0x02, (byte) 0xF8, (byte) 0xBA, (byte) 0x1F,
+            (byte) 0xCB, (byte) 0xCB, (byte) 0x7F, (byte) 0xC0, (byte) 0x3C, (byte) 0x2D,
+            (byte) 0x07, (byte) 0xA7, (byte) 0xAE, (byte) 0xEF, (byte) 0x60, (byte) 0x95,
+            (byte) 0xA7, (byte) 0x47, (byte) 0x09, (byte) 0xE1, (byte) 0x5D, (byte) 0xE5,
+            (byte) 0x92, (byte) 0x73, (byte) 0x7A, (byte) 0x86, (byte) 0xE1, (byte) 0xFD,
+            (byte) 0x72, (byte) 0xDE, (byte) 0x85, (byte) 0x16, (byte) 0x4E, (byte) 0xF4,
+            (byte) 0xA1, (byte) 0x12, (byte) 0x21, (byte) 0xFD, (byte) 0x50, (byte) 0x4D,
+            (byte) 0x04, (byte) 0x1C, (byte) 0xFD, (byte) 0xD3, (byte) 0x48, (byte) 0xD8,
+            (byte) 0xCB, (byte) 0xEE, (byte) 0xF5, (byte) 0xD7, (byte) 0x52, (byte) 0x66,
+            (byte) 0xD5, (byte) 0xBF, (byte) 0x22, (byte) 0xA8, (byte) 0xE4, (byte) 0xD0,
+            (byte) 0xF5, (byte) 0xA4, (byte) 0xF9, (byte) 0x0B, (byte) 0xB4, (byte) 0x84,
+            (byte) 0x84, (byte) 0xD7, (byte) 0x10, (byte) 0x14, (byte) 0x9B, (byte) 0xEA,
+            (byte) 0xCC, (byte) 0x7D, (byte) 0xDE, (byte) 0x30, (byte) 0xF9, (byte) 0x1B,
+            (byte) 0xE9, (byte) 0x94, (byte) 0x96, (byte) 0x1A, (byte) 0x6D, (byte) 0x72,
+            (byte) 0x18, (byte) 0x5E, (byte) 0xCC, (byte) 0x09, (byte) 0x04, (byte) 0xC6,
+            (byte) 0x41, (byte) 0x71, (byte) 0x76, (byte) 0xD1, (byte) 0x29, (byte) 0x3F,
+            (byte) 0x3B, (byte) 0x5E, (byte) 0x85, (byte) 0x4A, (byte) 0x30, (byte) 0x32,
+            (byte) 0x9D, (byte) 0x4F, (byte) 0xDB, (byte) 0xDE, (byte) 0x82, (byte) 0x66,
+            (byte) 0x39, (byte) 0xCB, (byte) 0x5C, (byte) 0xC9, (byte) 0xC5, (byte) 0x98,
+            (byte) 0x91, (byte) 0x8D, (byte) 0x32, (byte) 0xB5, (byte) 0x2F, (byte) 0xE4,
+            (byte) 0xDC, (byte) 0xB0, (byte) 0x6E, (byte) 0x21, (byte) 0xDE, (byte) 0x39,
+            (byte) 0x3C, (byte) 0x96, (byte) 0xA8, (byte) 0x32, (byte) 0xA8, (byte) 0xC1,
+            (byte) 0xD1, (byte) 0x6C, (byte) 0xA9, (byte) 0xAA, (byte) 0xF3, (byte) 0x5E,
+            (byte) 0x24, (byte) 0x70, (byte) 0xB7, (byte) 0xAB, (byte) 0x92, (byte) 0x63,
+            (byte) 0x08, (byte) 0x1E, (byte) 0x11, (byte) 0x3F, (byte) 0xB3, (byte) 0x5F,
+            (byte) 0xC7, (byte) 0x98, (byte) 0xE3, (byte) 0x1D, (byte) 0x2A, (byte) 0xC2,
+            (byte) 0x32, (byte) 0x1C, (byte) 0x3C, (byte) 0x95, (byte) 0x43, (byte) 0x16,
+            (byte) 0xE0, (byte) 0x46, (byte) 0x83, (byte) 0xC6, (byte) 0x36, (byte) 0x91,
+            (byte) 0xF4, (byte) 0xA0, (byte) 0xE1, (byte) 0x3C, (byte) 0xB8, (byte) 0x23,
+            (byte) 0xB2, (byte) 0x4F, (byte) 0x8B, (byte) 0x0C, (byte) 0x8C, (byte) 0x92,
+            (byte) 0x45, (byte) 0x24, (byte) 0x43, (byte) 0x68, (byte) 0x24, (byte) 0x06,
+            (byte) 0x84, (byte) 0x43, (byte) 0x96, (byte) 0x2C, (byte) 0x96, (byte) 0x55,
+            (byte) 0x2F, (byte) 0x32, (byte) 0xE8, (byte) 0xE0, (byte) 0xDE, (byte) 0xBF,
+            (byte) 0x52, (byte) 0x57, (byte) 0x2D, (byte) 0x08, (byte) 0x71, (byte) 0x25,
+            (byte) 0x96, (byte) 0x90, (byte) 0x54, (byte) 0x4A, (byte) 0xF1, (byte) 0x0E,
+            (byte) 0xC8, (byte) 0x58, (byte) 0x1A, (byte) 0xE7, (byte) 0x6A, (byte) 0xAB,
+            (byte) 0xA0, (byte) 0x68, (byte) 0xE0, (byte) 0xAD, (byte) 0xFD, (byte) 0xD6,
+            (byte) 0x39, (byte) 0x0F, (byte) 0x76, (byte) 0xE4, (byte) 0xC1, (byte) 0x70,
+            (byte) 0xCD, (byte) 0xDE, (byte) 0x80, (byte) 0x2B, (byte) 0xE2, (byte) 0x1C,
+            (byte) 0x87, (byte) 0x48, (byte) 0x03, (byte) 0x46, (byte) 0x0F, (byte) 0x2C,
+            (byte) 0x41, (byte) 0xF7, (byte) 0x4B, (byte) 0x1F, (byte) 0x93, (byte) 0xAE,
+            (byte) 0x3F, (byte) 0x57, (byte) 0x1F, (byte) 0x2D, (byte) 0xF5, (byte) 0x35,
+            (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xA3,
+            (byte) 0x82, (byte) 0x01, (byte) 0x41, (byte) 0x30, (byte) 0x82, (byte) 0x01,
+            (byte) 0x3D, (byte) 0x30, (byte) 0x1D, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x1D, (byte) 0x25, (byte) 0x04, (byte) 0x16, (byte) 0x30, (byte) 0x14,
+            (byte) 0x06, (byte) 0x08, (byte) 0x2B, (byte) 0x06, (byte) 0x01, (byte) 0x05,
+            (byte) 0x05, (byte) 0x07, (byte) 0x03, (byte) 0x01, (byte) 0x06, (byte) 0x08,
+            (byte) 0x2B, (byte) 0x06, (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07,
+            (byte) 0x03, (byte) 0x02, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03,
+            (byte) 0x55, (byte) 0x1D, (byte) 0x11, (byte) 0x04, (byte) 0x12, (byte) 0x30,
+            (byte) 0x10, (byte) 0x82, (byte) 0x0E, (byte) 0x77, (byte) 0x77, (byte) 0x77,
+            (byte) 0x2E, (byte) 0x67, (byte) 0x6F, (byte) 0x6F, (byte) 0x67, (byte) 0x6C,
+            (byte) 0x65, (byte) 0x2E, (byte) 0x63, (byte) 0x6F, (byte) 0x6D, (byte) 0x30,
+            (byte) 0x68, (byte) 0x06, (byte) 0x08, (byte) 0x2B, (byte) 0x06, (byte) 0x01,
+            (byte) 0x05, (byte) 0x05, (byte) 0x07, (byte) 0x01, (byte) 0x01, (byte) 0x04,
+            (byte) 0x5C, (byte) 0x30, (byte) 0x5A, (byte) 0x30, (byte) 0x2B, (byte) 0x06,
+            (byte) 0x08, (byte) 0x2B, (byte) 0x06, (byte) 0x01, (byte) 0x05, (byte) 0x05,
+            (byte) 0x07, (byte) 0x30, (byte) 0x02, (byte) 0x86, (byte) 0x1F, (byte) 0x68,
+            (byte) 0x74, (byte) 0x74, (byte) 0x70, (byte) 0x3A, (byte) 0x2F, (byte) 0x2F,
+            (byte) 0x70, (byte) 0x6B, (byte) 0x69, (byte) 0x2E, (byte) 0x67, (byte) 0x6F,
+            (byte) 0x6F, (byte) 0x67, (byte) 0x6C, (byte) 0x65, (byte) 0x2E, (byte) 0x63,
+            (byte) 0x6F, (byte) 0x6D, (byte) 0x2F, (byte) 0x47, (byte) 0x49, (byte) 0x41,
+            (byte) 0x47, (byte) 0x32, (byte) 0x2E, (byte) 0x63, (byte) 0x72, (byte) 0x74,
+            (byte) 0x30, (byte) 0x2B, (byte) 0x06, (byte) 0x08, (byte) 0x2B, (byte) 0x06,
+            (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, (byte) 0x30, (byte) 0x01,
+            (byte) 0x86, (byte) 0x1F, (byte) 0x68, (byte) 0x74, (byte) 0x74, (byte) 0x70,
+            (byte) 0x3A, (byte) 0x2F, (byte) 0x2F, (byte) 0x63, (byte) 0x6C, (byte) 0x69,
+            (byte) 0x65, (byte) 0x6E, (byte) 0x74, (byte) 0x73, (byte) 0x31, (byte) 0x2E,
+            (byte) 0x67, (byte) 0x6F, (byte) 0x6F, (byte) 0x67, (byte) 0x6C, (byte) 0x65,
+            (byte) 0x2E, (byte) 0x63, (byte) 0x6F, (byte) 0x6D, (byte) 0x2F, (byte) 0x6F,
+            (byte) 0x63, (byte) 0x73, (byte) 0x70, (byte) 0x30, (byte) 0x1D, (byte) 0x06,
+            (byte) 0x03, (byte) 0x55, (byte) 0x1D, (byte) 0x0E, (byte) 0x04, (byte) 0x16,
+            (byte) 0x04, (byte) 0x14, (byte) 0x3B, (byte) 0x6B, (byte) 0xE0, (byte) 0x9C,
+            (byte) 0xC6, (byte) 0xC6, (byte) 0x41, (byte) 0xC8, (byte) 0xEA, (byte) 0x5C,
+            (byte) 0xFB, (byte) 0x1A, (byte) 0x58, (byte) 0x15, (byte) 0xC2, (byte) 0x1B,
+            (byte) 0x9D, (byte) 0x43, (byte) 0x19, (byte) 0x85, (byte) 0x30, (byte) 0x0C,
+            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1D, (byte) 0x13, (byte) 0x01,
+            (byte) 0x01, (byte) 0xFF, (byte) 0x04, (byte) 0x02, (byte) 0x30, (byte) 0x00,
+            (byte) 0x30, (byte) 0x1F, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1D,
+            (byte) 0x23, (byte) 0x04, (byte) 0x18, (byte) 0x30, (byte) 0x16, (byte) 0x80,
+            (byte) 0x14, (byte) 0x4A, (byte) 0xDD, (byte) 0x06, (byte) 0x16, (byte) 0x1B,
+            (byte) 0xBC, (byte) 0xF6, (byte) 0x68, (byte) 0xB5, (byte) 0x76, (byte) 0xF5,
+            (byte) 0x81, (byte) 0xB6, (byte) 0xBB, (byte) 0x62, (byte) 0x1A, (byte) 0xBA,
+            (byte) 0x5A, (byte) 0x81, (byte) 0x2F, (byte) 0x30, (byte) 0x17, (byte) 0x06,
+            (byte) 0x03, (byte) 0x55, (byte) 0x1D, (byte) 0x20, (byte) 0x04, (byte) 0x10,
+            (byte) 0x30, (byte) 0x0E, (byte) 0x30, (byte) 0x0C, (byte) 0x06, (byte) 0x0A,
+            (byte) 0x2B, (byte) 0x06, (byte) 0x01, (byte) 0x04, (byte) 0x01, (byte) 0xD6,
+            (byte) 0x79, (byte) 0x02, (byte) 0x05, (byte) 0x01, (byte) 0x30, (byte) 0x30,
+            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1D, (byte) 0x1F, (byte) 0x04,
+            (byte) 0x29, (byte) 0x30, (byte) 0x27, (byte) 0x30, (byte) 0x25, (byte) 0xA0,
+            (byte) 0x23, (byte) 0xA0, (byte) 0x21, (byte) 0x86, (byte) 0x1F, (byte) 0x68,
+            (byte) 0x74, (byte) 0x74, (byte) 0x70, (byte) 0x3A, (byte) 0x2F, (byte) 0x2F,
+            (byte) 0x70, (byte) 0x6B, (byte) 0x69, (byte) 0x2E, (byte) 0x67, (byte) 0x6F,
+            (byte) 0x6F, (byte) 0x67, (byte) 0x6C, (byte) 0x65, (byte) 0x2E, (byte) 0x63,
+            (byte) 0x6F, (byte) 0x6D, (byte) 0x2F, (byte) 0x47, (byte) 0x49, (byte) 0x41,
+            (byte) 0x47, (byte) 0x32, (byte) 0x2E, (byte) 0x63, (byte) 0x72, (byte) 0x6C,
+            (byte) 0x30, (byte) 0x0D, (byte) 0x06, (byte) 0x09, (byte) 0x2A, (byte) 0x86,
+            (byte) 0x48, (byte) 0x86, (byte) 0xF7, (byte) 0x0D, (byte) 0x01, (byte) 0x01,
+            (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x82, (byte) 0x01,
+            (byte) 0x01, (byte) 0x00, (byte) 0x9A, (byte) 0x39, (byte) 0x70, (byte) 0x81,
+            (byte) 0x76, (byte) 0x8A, (byte) 0x94, (byte) 0xCB, (byte) 0x96, (byte) 0xF1,
+            (byte) 0xCA, (byte) 0xAF, (byte) 0x96, (byte) 0xAE, (byte) 0x1D, (byte) 0x73,
+            (byte) 0xB3, (byte) 0x2C, (byte) 0x82, (byte) 0x16, (byte) 0x29, (byte) 0xB5,
+            (byte) 0x3C, (byte) 0x7E, (byte) 0x55, (byte) 0x53, (byte) 0x6F, (byte) 0xB2,
+            (byte) 0xBC, (byte) 0x34, (byte) 0x96, (byte) 0xAE, (byte) 0x00, (byte) 0xD8,
+            (byte) 0xF2, (byte) 0x26, (byte) 0xD1, (byte) 0x18, (byte) 0x99, (byte) 0x9F,
+            (byte) 0x7D, (byte) 0xFD, (byte) 0xEB, (byte) 0xE0, (byte) 0xBB, (byte) 0x9D,
+            (byte) 0xE6, (byte) 0x46, (byte) 0xA5, (byte) 0x74, (byte) 0xAB, (byte) 0x3D,
+            (byte) 0x93, (byte) 0xC6, (byte) 0x25, (byte) 0x28, (byte) 0x3D, (byte) 0xC8,
+            (byte) 0x4C, (byte) 0x6E, (byte) 0xCF, (byte) 0xD1, (byte) 0x84, (byte) 0xFF,
+            (byte) 0x46, (byte) 0x4F, (byte) 0x21, (byte) 0x2E, (byte) 0x07, (byte) 0xC4,
+            (byte) 0xB8, (byte) 0xB7, (byte) 0x2A, (byte) 0xE5, (byte) 0xC7, (byte) 0x34,
+            (byte) 0xC6, (byte) 0xA9, (byte) 0x84, (byte) 0xE3, (byte) 0x6C, (byte) 0x49,
+            (byte) 0xF8, (byte) 0x4A, (byte) 0x36, (byte) 0xBB, (byte) 0x3A, (byte) 0xBD,
+            (byte) 0xAD, (byte) 0x8A, (byte) 0x2B, (byte) 0x73, (byte) 0x97, (byte) 0xA6,
+            (byte) 0x30, (byte) 0x2C, (byte) 0x5F, (byte) 0xE4, (byte) 0xBD, (byte) 0x13,
+            (byte) 0x24, (byte) 0xE5, (byte) 0xD9, (byte) 0xA8, (byte) 0x74, (byte) 0x29,
+            (byte) 0x38, (byte) 0x47, (byte) 0x2E, (byte) 0xA6, (byte) 0xD6, (byte) 0x50,
+            (byte) 0xE0, (byte) 0xE8, (byte) 0xDD, (byte) 0x60, (byte) 0xC7, (byte) 0xD2,
+            (byte) 0xC6, (byte) 0x4E, (byte) 0x54, (byte) 0xCE, (byte) 0xE7, (byte) 0x94,
+            (byte) 0x84, (byte) 0x0D, (byte) 0xE8, (byte) 0x81, (byte) 0x92, (byte) 0x91,
+            (byte) 0x71, (byte) 0x19, (byte) 0x1D, (byte) 0x07, (byte) 0x75, (byte) 0x9E,
+            (byte) 0x59, (byte) 0x1A, (byte) 0x7E, (byte) 0x9D, (byte) 0x84, (byte) 0x61,
+            (byte) 0xC7, (byte) 0x84, (byte) 0xAD, (byte) 0xA3, (byte) 0x6A, (byte) 0xED,
+            (byte) 0xD8, (byte) 0x0D, (byte) 0x0C, (byte) 0x2A, (byte) 0x66, (byte) 0x3D,
+            (byte) 0xD7, (byte) 0xAE, (byte) 0x46, (byte) 0x1D, (byte) 0x4A, (byte) 0x8C,
+            (byte) 0x2B, (byte) 0xD6, (byte) 0x1A, (byte) 0x69, (byte) 0x71, (byte) 0xC3,
+            (byte) 0x5E, (byte) 0xA0, (byte) 0x6E, (byte) 0xED, (byte) 0x27, (byte) 0x9F,
+            (byte) 0xAF, (byte) 0x5B, (byte) 0x92, (byte) 0xA0, (byte) 0x03, (byte) 0xFD,
+            (byte) 0x83, (byte) 0x22, (byte) 0x09, (byte) 0x29, (byte) 0xE8, (byte) 0xA1,
+            (byte) 0x32, (byte) 0x2B, (byte) 0xEC, (byte) 0x1A, (byte) 0xA2, (byte) 0x75,
+            (byte) 0x4C, (byte) 0x3E, (byte) 0x99, (byte) 0x71, (byte) 0xCE, (byte) 0x8B,
+            (byte) 0x31, (byte) 0xEF, (byte) 0x9D, (byte) 0x37, (byte) 0x63, (byte) 0xFC,
+            (byte) 0x71, (byte) 0x91, (byte) 0x10, (byte) 0x1E, (byte) 0xD0, (byte) 0xF5,
+            (byte) 0xCB, (byte) 0x6F, (byte) 0x7A, (byte) 0xBA, (byte) 0x5E, (byte) 0x0C,
+            (byte) 0x8A, (byte) 0xFA, (byte) 0xA4, (byte) 0xDE, (byte) 0x36, (byte) 0xAD,
+            (byte) 0x51, (byte) 0x52, (byte) 0xFC, (byte) 0xFE, (byte) 0x10, (byte) 0xB0,
+            (byte) 0x81, (byte) 0xC8, (byte) 0x7D, (byte) 0x03, (byte) 0xC3, (byte) 0xB8,
+            (byte) 0x3C, (byte) 0x66, (byte) 0x6A, (byte) 0xF5, (byte) 0x6A, (byte) 0x81,
+            (byte) 0x7C, (byte) 0x45, (byte) 0xA6, (byte) 0x23, (byte) 0x21, (byte) 0xE1,
+            (byte) 0xD5, (byte) 0xD3, (byte) 0xED, (byte) 0x6E, (byte) 0x0D, (byte) 0x65,
+            (byte) 0x39, (byte) 0x77, (byte) 0x58, (byte) 0x09, (byte) 0x6B, (byte) 0x63,
+            (byte) 0xA4, (byte) 0x02, (byte) 0x04, (byte) 0x00, (byte) 0xA5, (byte) 0x03,
+            (byte) 0x02, (byte) 0x01, (byte) 0x14, (byte) 0xA9, (byte) 0x05, (byte) 0x02,
+            (byte) 0x03, (byte) 0x01, (byte) 0x89, (byte) 0xC0, (byte) 0xAA, (byte) 0x81,
+            (byte) 0xA7, (byte) 0x04, (byte) 0x81, (byte) 0xA4, (byte) 0x1C, (byte) 0x14,
+            (byte) 0x42, (byte) 0xFA, (byte) 0x1E, (byte) 0x3A, (byte) 0x4D, (byte) 0x0A,
+            (byte) 0x83, (byte) 0x7E, (byte) 0x92, (byte) 0x61, (byte) 0x37, (byte) 0x0B,
+            (byte) 0x12, (byte) 0x45, (byte) 0xEA, (byte) 0x2B, (byte) 0x03, (byte) 0x81,
+            (byte) 0x7C, (byte) 0x5F, (byte) 0x6F, (byte) 0x13, (byte) 0x82, (byte) 0x97,
+            (byte) 0xD0, (byte) 0xDC, (byte) 0x5E, (byte) 0x2F, (byte) 0x08, (byte) 0xDC,
+            (byte) 0x0D, (byte) 0x3A, (byte) 0x6C, (byte) 0xBA, (byte) 0x1D, (byte) 0xEA,
+            (byte) 0x5C, (byte) 0x46, (byte) 0x99, (byte) 0xF7, (byte) 0xDD, (byte) 0xAB,
+            (byte) 0xD4, (byte) 0xDD, (byte) 0xFC, (byte) 0x54, (byte) 0x37, (byte) 0x32,
+            (byte) 0x4B, (byte) 0xA3, (byte) 0xFB, (byte) 0x23, (byte) 0xA1, (byte) 0xC1,
+            (byte) 0x60, (byte) 0xDF, (byte) 0x41, (byte) 0xB0, (byte) 0xD1, (byte) 0xCC,
+            (byte) 0xDF, (byte) 0xAD, (byte) 0xB3, (byte) 0x66, (byte) 0x76, (byte) 0x36,
+            (byte) 0xEC, (byte) 0x6A, (byte) 0x53, (byte) 0xC3, (byte) 0xE2, (byte) 0xB0,
+            (byte) 0x77, (byte) 0xBE, (byte) 0x75, (byte) 0x08, (byte) 0xBA, (byte) 0x17,
+            (byte) 0x14, (byte) 0xFA, (byte) 0x1A, (byte) 0x30, (byte) 0xE7, (byte) 0xB9,
+            (byte) 0xED, (byte) 0xD6, (byte) 0xC1, (byte) 0xA5, (byte) 0x7A, (byte) 0x2B,
+            (byte) 0xA3, (byte) 0xA3, (byte) 0xDD, (byte) 0xDC, (byte) 0x14, (byte) 0xDB,
+            (byte) 0x7F, (byte) 0xF4, (byte) 0xF3, (byte) 0xAF, (byte) 0xCF, (byte) 0x0A,
+            (byte) 0xD3, (byte) 0xAC, (byte) 0x84, (byte) 0x39, (byte) 0x30, (byte) 0xCA,
+            (byte) 0x3C, (byte) 0xD8, (byte) 0xF7, (byte) 0xFA, (byte) 0x29, (byte) 0xDB,
+            (byte) 0x31, (byte) 0xA5, (byte) 0x62, (byte) 0x82, (byte) 0xD2, (byte) 0xB8,
+            (byte) 0x3C, (byte) 0xBC, (byte) 0x8F, (byte) 0xAB, (byte) 0xE4, (byte) 0xE8,
+            (byte) 0xA7, (byte) 0x2C, (byte) 0xEF, (byte) 0xC7, (byte) 0xD5, (byte) 0x12,
+            (byte) 0x16, (byte) 0x04, (byte) 0x6F, (byte) 0xCA, (byte) 0xEA, (byte) 0x31,
+            (byte) 0x9F, (byte) 0x41, (byte) 0xE0, (byte) 0x6F, (byte) 0xE4, (byte) 0x74,
+            (byte) 0x03, (byte) 0x78, (byte) 0xFA, (byte) 0x48, (byte) 0xB4, (byte) 0x6E,
+            (byte) 0xC8, (byte) 0xE7, (byte) 0x40, (byte) 0x8B, (byte) 0x88, (byte) 0x2F,
+            (byte) 0xED, (byte) 0x8E, (byte) 0x68, (byte) 0x96, (byte) 0x2C, (byte) 0xA7,
+            (byte) 0xB6, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0x00};
+
+    private static final byte[] DUMMY_CERT =
+            new byte[] {(byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x58, (byte) 0x30,
+                    (byte) 0x82, (byte) 0x01, (byte) 0xC1, (byte) 0xA0, (byte) 0x03, (byte) 0x02,
+                    (byte) 0x01, (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xFB,
+                    (byte) 0xB0, (byte) 0x4C, (byte) 0x2E, (byte) 0xAB, (byte) 0x10, (byte) 0x9B,
+                    (byte) 0x0C, (byte) 0x30, (byte) 0x0D, (byte) 0x06, (byte) 0x09, (byte) 0x2A,
+                    (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xF7, (byte) 0x0D, (byte) 0x01,
+                    (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x45,
+                    (byte) 0x31, (byte) 0x0B, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03,
+                    (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41,
+                    (byte) 0x55, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06,
+                    (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0C, (byte) 0x0A,
+                    (byte) 0x53, (byte) 0x6F, (byte) 0x6D, (byte) 0x65, (byte) 0x2D, (byte) 0x53,
+                    (byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21,
+                    (byte) 0x30, (byte) 0x1F, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
+                    (byte) 0x0A, (byte) 0x0C, (byte) 0x18, (byte) 0x49, (byte) 0x6E, (byte) 0x74,
+                    (byte) 0x65, (byte) 0x72, (byte) 0x6E, (byte) 0x65, (byte) 0x74, (byte) 0x20,
+                    (byte) 0x57, (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74,
+                    (byte) 0x73, (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20,
+                    (byte) 0x4C, (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x1E, (byte) 0x17,
+                    (byte) 0x0D, (byte) 0x31, (byte) 0x34, (byte) 0x30, (byte) 0x34, (byte) 0x32,
+                    (byte) 0x33, (byte) 0x32, (byte) 0x30, (byte) 0x35, (byte) 0x30, (byte) 0x34,
+                    (byte) 0x30, (byte) 0x5A, (byte) 0x17, (byte) 0x0D, (byte) 0x31, (byte) 0x37,
+                    (byte) 0x30, (byte) 0x34, (byte) 0x32, (byte) 0x32, (byte) 0x32, (byte) 0x30,
+                    (byte) 0x35, (byte) 0x30, (byte) 0x34, (byte) 0x30, (byte) 0x5A, (byte) 0x30,
+                    (byte) 0x45, (byte) 0x31, (byte) 0x0B, (byte) 0x30, (byte) 0x09, (byte) 0x06,
+                    (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02,
+                    (byte) 0x41, (byte) 0x55, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11,
+                    (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0C,
+                    (byte) 0x0A, (byte) 0x53, (byte) 0x6F, (byte) 0x6D, (byte) 0x65, (byte) 0x2D,
+                    (byte) 0x53, (byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31,
+                    (byte) 0x21, (byte) 0x30, (byte) 0x1F, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+                    (byte) 0x04, (byte) 0x0A, (byte) 0x0C, (byte) 0x18, (byte) 0x49, (byte) 0x6E,
+                    (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x6E, (byte) 0x65, (byte) 0x74,
+                    (byte) 0x20, (byte) 0x57, (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69,
+                    (byte) 0x74, (byte) 0x73, (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79,
+                    (byte) 0x20, (byte) 0x4C, (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x81,
+                    (byte) 0x9F, (byte) 0x30, (byte) 0x0D, (byte) 0x06, (byte) 0x09, (byte) 0x2A,
+                    (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xF7, (byte) 0x0D, (byte) 0x01,
+                    (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81,
+                    (byte) 0x8D, (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, (byte) 0x02,
+                    (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xD8, (byte) 0x2B, (byte) 0xC8,
+                    (byte) 0xA6, (byte) 0x32, (byte) 0xE4, (byte) 0x62, (byte) 0xFF, (byte) 0x4D,
+                    (byte) 0xF3, (byte) 0xD0, (byte) 0xAD, (byte) 0x59, (byte) 0x8B, (byte) 0x45,
+                    (byte) 0xA7, (byte) 0xBD, (byte) 0xF1, (byte) 0x47, (byte) 0xBF, (byte) 0x09,
+                    (byte) 0x58, (byte) 0x7B, (byte) 0x22, (byte) 0xBD, (byte) 0x35, (byte) 0xAE,
+                    (byte) 0x97, (byte) 0x25, (byte) 0x86, (byte) 0x94, (byte) 0xA0, (byte) 0x80,
+                    (byte) 0xC0, (byte) 0xB4, (byte) 0x1F, (byte) 0x76, (byte) 0x91, (byte) 0x67,
+                    (byte) 0x46, (byte) 0x31, (byte) 0xD0, (byte) 0x10, (byte) 0x84, (byte) 0xB7,
+                    (byte) 0x22, (byte) 0x1E, (byte) 0x70, (byte) 0x23, (byte) 0x91, (byte) 0x72,
+                    (byte) 0xC8, (byte) 0xE9, (byte) 0x6D, (byte) 0x79, (byte) 0x3A, (byte) 0x85,
+                    (byte) 0x77, (byte) 0x80, (byte) 0x0F, (byte) 0xC4, (byte) 0x95, (byte) 0x16,
+                    (byte) 0x75, (byte) 0xC5, (byte) 0x4A, (byte) 0x71, (byte) 0x4C, (byte) 0xC8,
+                    (byte) 0x63, (byte) 0x3F, (byte) 0xA3, (byte) 0xF2, (byte) 0x63, (byte) 0x9C,
+                    (byte) 0x2A, (byte) 0x4F, (byte) 0x9A, (byte) 0xFA, (byte) 0xCB, (byte) 0xC1,
+                    (byte) 0x71, (byte) 0x6E, (byte) 0x28, (byte) 0x85, (byte) 0x28, (byte) 0xA0,
+                    (byte) 0x27, (byte) 0x1E, (byte) 0x65, (byte) 0x1C, (byte) 0xAE, (byte) 0x07,
+                    (byte) 0xD5, (byte) 0x5B, (byte) 0x6F, (byte) 0x2D, (byte) 0x43, (byte) 0xED,
+                    (byte) 0x2B, (byte) 0x90, (byte) 0xB1, (byte) 0x8C, (byte) 0xAF, (byte) 0x24,
+                    (byte) 0x6D, (byte) 0xAE, (byte) 0xE9, (byte) 0x17, (byte) 0x3A, (byte) 0x05,
+                    (byte) 0xC1, (byte) 0xBF, (byte) 0xB8, (byte) 0x1C, (byte) 0xAE, (byte) 0x65,
+                    (byte) 0x3B, (byte) 0x1B, (byte) 0x58, (byte) 0xC2, (byte) 0xD9, (byte) 0xAE,
+                    (byte) 0xD6, (byte) 0xAA, (byte) 0x67, (byte) 0x88, (byte) 0xF1, (byte) 0x02,
+                    (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xA3, (byte) 0x50,
+                    (byte) 0x30, (byte) 0x4E, (byte) 0x30, (byte) 0x1D, (byte) 0x06, (byte) 0x03,
+                    (byte) 0x55, (byte) 0x1D, (byte) 0x0E, (byte) 0x04, (byte) 0x16, (byte) 0x04,
+                    (byte) 0x14, (byte) 0x8B, (byte) 0x75, (byte) 0xD5, (byte) 0xAC, (byte) 0xCB,
+                    (byte) 0x08, (byte) 0xBE, (byte) 0x0E, (byte) 0x1F, (byte) 0x65, (byte) 0xB7,
+                    (byte) 0xFA, (byte) 0x56, (byte) 0xBE, (byte) 0x6C, (byte) 0xA7, (byte) 0x75,
+                    (byte) 0xDA, (byte) 0x85, (byte) 0xAF, (byte) 0x30, (byte) 0x1F, (byte) 0x06,
+                    (byte) 0x03, (byte) 0x55, (byte) 0x1D, (byte) 0x23, (byte) 0x04, (byte) 0x18,
+                    (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x8B, (byte) 0x75,
+                    (byte) 0xD5, (byte) 0xAC, (byte) 0xCB, (byte) 0x08, (byte) 0xBE, (byte) 0x0E,
+                    (byte) 0x1F, (byte) 0x65, (byte) 0xB7, (byte) 0xFA, (byte) 0x56, (byte) 0xBE,
+                    (byte) 0x6C, (byte) 0xA7, (byte) 0x75, (byte) 0xDA, (byte) 0x85, (byte) 0xAF,
+                    (byte) 0x30, (byte) 0x0C, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1D,
+                    (byte) 0x13, (byte) 0x04, (byte) 0x05, (byte) 0x30, (byte) 0x03, (byte) 0x01,
+                    (byte) 0x01, (byte) 0xFF, (byte) 0x30, (byte) 0x0D, (byte) 0x06, (byte) 0x09,
+                    (byte) 0x2A, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xF7, (byte) 0x0D,
+                    (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03,
+                    (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0x3B, (byte) 0xE8, (byte) 0x78,
+                    (byte) 0x6D, (byte) 0x95, (byte) 0xD6, (byte) 0x3D, (byte) 0x6A, (byte) 0xF7,
+                    (byte) 0x13, (byte) 0x19, (byte) 0x2C, (byte) 0x1B, (byte) 0xC2, (byte) 0x88,
+                    (byte) 0xAE, (byte) 0x22, (byte) 0xAB, (byte) 0xF4, (byte) 0x8D, (byte) 0x32,
+                    (byte) 0xF5, (byte) 0x7C, (byte) 0x71, (byte) 0x67, (byte) 0xCF, (byte) 0x2D,
+                    (byte) 0xD1, (byte) 0x1C, (byte) 0xC2, (byte) 0xC3, (byte) 0x87, (byte) 0xE2,
+                    (byte) 0xE9, (byte) 0xBE, (byte) 0x89, (byte) 0x5C, (byte) 0xE4, (byte) 0x34,
+                    (byte) 0xAB, (byte) 0x48, (byte) 0x91, (byte) 0xC2, (byte) 0x3F, (byte) 0x95,
+                    (byte) 0xAE, (byte) 0x2B, (byte) 0x47, (byte) 0x9E, (byte) 0x25, (byte) 0x78,
+                    (byte) 0x6B, (byte) 0x4F, (byte) 0x9A, (byte) 0x10, (byte) 0xA4, (byte) 0x72,
+                    (byte) 0xFD, (byte) 0xCF, (byte) 0xF7, (byte) 0x02, (byte) 0x0C, (byte) 0xB0,
+                    (byte) 0x0A, (byte) 0x08, (byte) 0xA4, (byte) 0x5A, (byte) 0xE2, (byte) 0xE5,
+                    (byte) 0x74, (byte) 0x7E, (byte) 0x11, (byte) 0x1D, (byte) 0x39, (byte) 0x60,
+                    (byte) 0x6A, (byte) 0xC9, (byte) 0x1F, (byte) 0x69, (byte) 0xF3, (byte) 0x2E,
+                    (byte) 0x63, (byte) 0x26, (byte) 0xDC, (byte) 0x9E, (byte) 0xEF, (byte) 0x6B,
+                    (byte) 0x7A, (byte) 0x0A, (byte) 0xE1, (byte) 0x54, (byte) 0x57, (byte) 0x98,
+                    (byte) 0xAA, (byte) 0x72, (byte) 0x91, (byte) 0x78, (byte) 0x04, (byte) 0x7E,
+                    (byte) 0x1F, (byte) 0x8F, (byte) 0x65, (byte) 0x4D, (byte) 0x1F, (byte) 0x0B,
+                    (byte) 0x12, (byte) 0xAC, (byte) 0x9C, (byte) 0x24, (byte) 0x0F, (byte) 0x84,
+                    (byte) 0x14, (byte) 0x1A, (byte) 0x55, (byte) 0x2D, (byte) 0x1F, (byte) 0xBB,
+                    (byte) 0xF0, (byte) 0x9D, (byte) 0x09, (byte) 0xB2, (byte) 0x08, (byte) 0x5C,
+                    (byte) 0x59, (byte) 0x32, (byte) 0x65, (byte) 0x80, (byte) 0x26};
+
+    private static final byte[] DUMMY_OCSP_DATA = new byte[1];
+
+    private static final byte[] DUMMY_TLS_SCT_DATA = new byte[1];
+
+    @After
+    public void tearDown() throws Exception {
+        assertEquals(0, NativeCrypto.ERR_peek_last_error());
+    }
+
+    private static TestSessionBuilder getType1() {
+        return new TestSessionBuilder()
+                .setType(0x01)
+                .setSessionData(kOpenSSLSession)
+                .addCertificate(DUMMY_CERT);
+    }
+
+    private static TestSessionBuilder getType2() {
+        return new TestSessionBuilder()
+                .setType(0x02)
+                .setSessionData(kOpenSSLSession)
+                .addCertificate(DUMMY_CERT)
+                .addOcspData(DUMMY_OCSP_DATA);
+    }
+
+    private static TestSessionBuilder getType3() {
+        return new TestSessionBuilder()
+                .setType(0x03)
+                .setSessionData(kOpenSSLSession)
+                .addCertificate(DUMMY_CERT)
+                .addOcspData(DUMMY_OCSP_DATA)
+                .setTlsSctData(DUMMY_TLS_SCT_DATA);
+    }
+
+    @Test
+    public void toSession_EmptyArray_Invalid_Failure() throws Exception {
+        assertInvalidSession(new byte[0]);
+    }
+
+    @Test
+    public void toSession_Type1_Valid_Success() throws Exception {
+        assertValidSession(getType1().build());
+    }
+
+    @Test
+    public void toSession_Type2_Valid_Success() throws Exception {
+        assertValidSession(getType2().build());
+    }
+
+    @Test
+    public void toSession_Type3_Valid_Success() throws Exception {
+        assertValidSession(getType3().build());
+    }
+
+    private void assertTruncatedSessionFails(byte[] validSession) {
+        for (int i = 0; i < validSession.length - 1; i++) {
+            byte[] truncatedSession = new byte[i];
+            System.arraycopy(validSession, 0, truncatedSession, 0, i);
+            assertNull("Truncating to " + i + " bytes of " + validSession.length
+                            + " should not succeed",
+                    SslSessionWrapper.newInstance(null, truncatedSession, "www.google.com", 443));
+        }
+    }
+
+    @Test
+    public void toSession_Type3_Truncated_Failure() throws Exception {
+        assertTruncatedSessionFails(getType3().build());
+    }
+
+    private static void assertValidSession(byte[] data) {
+        assertNotNull(SslSessionWrapper.newInstance(null, data, "www.google.com", 443));
+    }
+
+    private static void assertInvalidSession(byte[] data) {
+        assertNull(SslSessionWrapper.newInstance(null, data, "www.google.com", 443));
+    }
+
+    @Test
+    public void toSession_UnknownType_Failure() throws Exception {
+        assertInvalidSession(getType3().setType((byte) 0xEE).build());
+    }
+
+    @Test
+    public void toSession_CertificatesCountTooLarge_Failure() throws Exception {
+        assertInvalidSession(getType3().setCertificatesLength(16834).build());
+    }
+
+    @Test
+    public void toSession_CertificatesCountNegative_Failure() throws Exception {
+        assertInvalidSession(getType3().setCertificatesLength(-1).build());
+    }
+
+    @Test
+    public void toSession_CertificateSizeNegative_Failure() throws Exception {
+        assertInvalidSession(getType3().setCertificateLength(0, -1).build());
+    }
+
+    @Test
+    public void toSession_CertificateSizeTooLarge_Failure() throws Exception {
+        assertInvalidSession(getType3().setCertificateLength(0, 16834).build());
+    }
+
+    @Test
+    public void toSession_SessionDataSizeTooLarge_Failure() throws Exception {
+        assertInvalidSession(getType3().setSessionDataLength(16834).build());
+    }
+
+    @Test
+    public void toSession_SessionDataSizeNegative_Failure() throws Exception {
+        assertInvalidSession(getType3().setSessionDataLength(-1).build());
+    }
+
+    @Test
+    public void toSession_OcspDatasNumberTooMany_Failure() throws Exception {
+        assertInvalidSession(getType3().setOcspDatasLength(32791).build());
+    }
+
+    @Test
+    public void toSession_OcspDatasNumberNegative_Failure() throws Exception {
+        assertInvalidSession(getType3().setOcspDatasLength(-1).build());
+    }
+
+    @Test
+    public void toSession_OcspDataSizeNegative_Failure() throws Exception {
+        assertInvalidSession(getType3().setOcspDataLength(0, -1).build());
+    }
+
+    @Test
+    public void toSession_OcspDataSizeTooLarge_Failure() throws Exception {
+        assertInvalidSession(getType3().setOcspDataLength(0, 92948).build());
+    }
+
+    @Test
+    public void toSession_TlsSctDataSizeNegative_Failure() throws Exception {
+        assertInvalidSession(getType3().setTlsSctDataLength(-1).build());
+    }
+
+    @Test
+    public void toSession_TlsSctDataSizeTooLarge_Failure() throws Exception {
+        assertInvalidSession(getType3().setTlsSctDataLength(931148).build());
+    }
+
+    @Test
+    public void toSession_Type2OcspDataEmpty_Success() throws Exception {
+        assertValidSession(getType1().setType(0x02).setOcspDataEmpty().build());
+    }
+
+    @Test
+    public void toSession_Type3TlsSctDataEmpty_Success() throws Exception {
+        assertValidSession(getType2().setType(0x03).setTlsSctDataEmpty().build());
+    }
+
+    @Test
+    public void toSession_Type3OcspAndTlsSctDataEmpty_Success() throws Exception {
+        assertValidSession(
+                getType1().setType(0x03).setOcspDataEmpty().setTlsSctDataEmpty().build());
+    }
+
+    private static void assertTrailingDataFails(byte[] validSession) {
+        byte[] invalidSession = new byte[validSession.length + 1];
+        System.arraycopy(validSession, 0, invalidSession, 0, validSession.length);
+        assertInvalidSession(invalidSession);
+    }
+
+    @Test
+    public void toSession_Type1TrailingData_Failure() throws Exception {
+        assertTrailingDataFails(getType1().build());
+    }
+
+    @Test
+    public void toSession_Type2TrailingData_Failure() throws Exception {
+        assertTrailingDataFails(getType2().build());
+    }
+
+    @Test
+    public void toSession_Type3TrailingData_Failure() throws Exception {
+        assertTrailingDataFails(getType3().build());
+    }
+
+    @Test
+    public void test_reserializableFromByteArray_roundTrip_type1() throws Exception {
+        // Converting OPEN_SSL (type 1) -> OPEN_SSL_WITH_TLS_SCT (type 3) adds
+        // eight zero-bytes:
+        //  1.) 4 bytes for int32 value 0 == countOcspResponses
+        //  2.) 4 bytes for int32 value 0 == tlsSctDataLength
+        // since OPEN_SSL (type 1) cannot contain OSCP or TLS SCT data.
+        check_reserializableFromByteArray_roundTrip(getType1().build(), new byte[8]);
+    }
+
+    @Test
+    public void test_reserializableFromByteArray_roundTrip_type2() throws Exception {
+        // Converting OPEN_SSL_WITH_OCSP (type 2) -> OPEN_SSL_WITH_TLS_SCT (type 3) adds
+        // four zero-bytes for int32 value 0 == tlsSctDataLength
+        // since OPEN_SSL_WITH_OCSP (type 2) cannot contain TLS SCT data.
+        check_reserializableFromByteArray_roundTrip(getType2().build(), new byte[4]);
+    }
+
+    @Test
+    public void test_reserializableFromByteArray_roundTrip_type3() throws Exception {
+        check_reserializableFromByteArray_roundTrip(getType3().build(), new byte[0]);
+    }
+
+    private static void check_reserializableFromByteArray_roundTrip(
+            byte[] data, byte[] expectedTrailingBytesAfterReserialization) throws Exception {
+        SslSessionWrapper session =
+                SslSessionWrapper.newInstance(null, data, "www.example.com", 12345);
+        byte[] sessionBytes = session.toBytes();
+
+        SslSessionWrapper session2 =
+                SslSessionWrapper.newInstance(null, sessionBytes, "www.example.com", 12345);
+        byte[] sessionBytes2 = session2.toBytes();
+
+        assertSSLSessionEquals(session, session2);
+        assertByteArrayEquals(sessionBytes, sessionBytes2);
+
+        assertEquals("www.example.com", session.getPeerHost());
+        assertEquals(12345, session.getPeerPort());
+        assertTrue(sessionBytes.length >= data.length);
+
+        byte[] expectedReserializedData = concat(data, expectedTrailingBytesAfterReserialization);
+        // AbstractSessionContext.toBytes() always writes type 3 == OPEN_SSL_WITH_TLS_SCT
+        expectedReserializedData[3] = 3;
+        assertByteArrayEquals(expectedReserializedData, sessionBytes);
+    }
+
+    private static byte[] concat(byte[] a, byte[] b) {
+        byte[] result = new byte[a.length + b.length];
+        System.arraycopy(a, 0, result, 0, a.length);
+        System.arraycopy(b, 0, result, a.length, b.length);
+        return result;
+    }
+
+    private static void assertSSLSessionEquals(SslSessionWrapper a, SslSessionWrapper b)
+            throws Exception {
+        assertEquals(a.getCipherSuite(), b.getCipherSuite());
+        assertByteArrayEquals(a.getId(), b.getId());
+        assertEquals(a.getPeerHost(), b.getPeerHost());
+        assertEquals(a.getPeerPort(), b.getPeerPort());
+        assertEquals(a.getProtocol(), b.getProtocol());
+    }
+
+    private static void assertByteArrayEquals(byte[] expected, byte[] actual) {
+        // If running on OpenJDK 8+, could use java.util.Base64 for better failure messages:
+        // assertEquals(Base64.encode(expected), Base64.encode(actual));
+        assertTrue("Expected " + Arrays.toString(expected) + ", got " + Arrays.toString(actual),
+                Arrays.equals(expected, actual));
+    }
+}
diff --git a/platform/src/main/java/org/conscrypt/Platform.java b/platform/src/main/java/org/conscrypt/Platform.java
index 5a62468..d53e69a 100644
--- a/platform/src/main/java/org/conscrypt/Platform.java
+++ b/platform/src/main/java/org/conscrypt/Platform.java
@@ -334,14 +334,15 @@
      * Pre-Java 8 backward compatibility.
      */
 
-    static SSLSession wrapSSLSession(AbstractOpenSSLSession sslSession) {
-        return new OpenSSLExtendedSessionImpl(sslSession);
+    static SSLSession wrapSSLSession(ActiveSession sslSession) {
+        return new DelegatingExtendedSSLSession(sslSession);
     }
 
     static SSLSession unwrapSSLSession(SSLSession sslSession) {
-        if (sslSession instanceof OpenSSLExtendedSessionImpl) {
-            return ((OpenSSLExtendedSessionImpl) sslSession).getDelegate();
+        if (sslSession instanceof DelegatingExtendedSSLSession) {
+            return ((DelegatingExtendedSSLSession) sslSession).getDelegate();
         }
+
         return sslSession;
     }
 
diff --git a/platform/src/main/java/org/conscrypt/TrustManagerImpl.java b/platform/src/main/java/org/conscrypt/TrustManagerImpl.java
index 010c75a..95f7f48 100644
--- a/platform/src/main/java/org/conscrypt/TrustManagerImpl.java
+++ b/platform/src/main/java/org/conscrypt/TrustManagerImpl.java
@@ -420,8 +420,8 @@
 
     private byte[] getOcspDataFromSession(SSLSession session) {
         List<byte[]> ocspResponses = null;
-        if (session instanceof AbstractOpenSSLSession) {
-            AbstractOpenSSLSession opensslSession = (AbstractOpenSSLSession) session;
+        if (session instanceof ActiveSession) {
+            ActiveSession opensslSession = (ActiveSession) session;
             ocspResponses = opensslSession.getStatusResponses();
         } else {
             Method m_getResponses;
@@ -447,14 +447,14 @@
     }
 
     private byte[] getTlsSctDataFromSession(SSLSession session) {
-        if (session instanceof AbstractOpenSSLSession) {
-            AbstractOpenSSLSession opensslSession = (AbstractOpenSSLSession) session;
-            return opensslSession.getTlsSctData();
+        if (session instanceof ActiveSession) {
+            ActiveSession opensslSession = (ActiveSession) session;
+            return opensslSession.getPeerSignedCertificateTimestamp();
         }
 
         byte[] data = null;
         try {
-            Method m_getTlsSctData = session.getClass().getDeclaredMethod("getTlsSctData");
+            Method m_getTlsSctData = session.getClass().getDeclaredMethod("getPeerSignedCertificateTimestamp");
             m_getTlsSctData.setAccessible(true);
             Object rawData = m_getTlsSctData.invoke(session);
             if (rawData instanceof byte[]) {
@@ -789,7 +789,7 @@
 
 
     /**
-     * Comparator for sorting {@link TrustAnchor}s using a {@link CertificateComparator}.
+     * Comparator for sorting {@link TrustAnchor}s using a {@link CertificatePriorityComparator}.
      */
     private static class TrustAnchorComparator implements Comparator<TrustAnchor> {
         private static final CertificatePriorityComparator CERT_COMPARATOR =
diff --git a/testing/src/main/java/libcore/javax/net/ssl/TestSSLSessions.java b/testing/src/main/java/libcore/javax/net/ssl/TestSSLSessions.java
index 34596bc..8f45a2e 100644
--- a/testing/src/main/java/libcore/javax/net/ssl/TestSSLSessions.java
+++ b/testing/src/main/java/libcore/javax/net/ssl/TestSSLSessions.java
@@ -27,19 +27,23 @@
      * An invalid session that is not connected
      */
     public final SSLSession invalid;
+
     /**
      * The server side of a connected session
      */
     public final SSLSession server;
+
     /**
      * The client side of a connected session
      */
     public final SSLSession client;
+
     /**
      * The associated SSLSocketTest.Helper that is the source of
      * the client and server SSLSessions.
      */
     public final TestSSLSocketPair s;
+
     private TestSSLSessions(SSLSession invalid,
             SSLSession server,
             SSLSession client,
@@ -49,15 +53,17 @@
         this.client = client;
         this.s = s;
     }
+
     public void close() {
         s.close();
     }
-    public static final TestSSLSessions create() {
+
+    public static TestSSLSessions create() {
         try {
             SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault();
             SSLSocket ssl = (SSLSocket) sf.createSocket();
             SSLSession invalid = ssl.getSession();
-            TestSSLSocketPair s = TestSSLSocketPair.create();
+            TestSSLSocketPair s = TestSSLSocketPair.create().connect();
             return new TestSSLSessions(invalid, s.server.getSession(), s.client.getSession(), s);
         } catch (Exception e) {
             throw new RuntimeException(e);
diff --git a/testing/src/main/java/libcore/javax/net/ssl/TestSSLSocketPair.java b/testing/src/main/java/libcore/javax/net/ssl/TestSSLSocketPair.java
index 66e7ac7..c6d59fe 100644
--- a/testing/src/main/java/libcore/javax/net/ssl/TestSSLSocketPair.java
+++ b/testing/src/main/java/libcore/javax/net/ssl/TestSSLSocketPair.java
@@ -15,6 +15,7 @@
  */
 package libcore.javax.net.ssl;
 
+import java.io.IOException;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -31,9 +32,7 @@
     public final TestSSLContext c;
     public final SSLSocket server;
     public final SSLSocket client;
-    private TestSSLSocketPair (TestSSLContext c,
-            SSLSocket server,
-            SSLSocket client) {
+    private TestSSLSocketPair(TestSSLContext c, SSLSocket server, SSLSocket client) {
         this.c = c;
         this.server = server;
         this.client = client;
@@ -47,14 +46,15 @@
             throw new RuntimeException(e);
         }
     }
-    /**
-     * based on test_SSLSocket_startHandshake
-     */
-    public static TestSSLSocketPair create () {
-        TestSSLContext c = TestSSLContext.create();
-        SSLSocket[] sockets = connect(c, null, null);
-        return new TestSSLSocketPair(c, sockets[0], sockets[1]);
+
+    public SSLSocket[] sockets() {
+        return new SSLSocket[] {server, client};
     }
+
+    public TestSSLSocketPair connect() {
+        return connect(null, null);
+    }
+
     /**
      * Create a new connected server/client socket pair within a
      * existing SSLContext. Optionally specify clientCipherSuites to
@@ -62,13 +62,9 @@
      * caching. Optionally specify serverCipherSuites for testing
      * cipher suite negotiation.
      */
-    public static SSLSocket[] connect (final TestSSLContext context,
-            final String[] clientCipherSuites,
-            final String[] serverCipherSuites) {
+    public TestSSLSocketPair connect(
+            final String[] clientCipherSuites, final String[] serverCipherSuites) {
         try {
-            final SSLSocket client = (SSLSocket)
-                    context.clientContext.getSocketFactory().createSocket(context.host, context.port);
-            final SSLSocket server = (SSLSocket) context.serverSocket.accept();
             ExecutorService executor = Executors.newFixedThreadPool(2);
             Future<Void> s = executor.submit(new Callable<Void>() {
                 @Override
@@ -115,11 +111,29 @@
             if (clientException != null) {
                 throw clientException;
             }
-            return new SSLSocket[] { server, client };
+            return this;
         } catch (RuntimeException e) {
             throw e;
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
+
+    public static TestSSLSocketPair create() {
+        return create(TestSSLContext.create());
+    }
+
+    /**
+     * based on test_SSLSocket_startHandshake
+     */
+    public static TestSSLSocketPair create(TestSSLContext context) {
+        try {
+            SSLSocket client = (SSLSocket) context.clientContext.getSocketFactory().createSocket(
+                    context.host, context.port);
+            SSLSocket server = (SSLSocket) context.serverSocket.accept();
+            return new TestSSLSocketPair(context, server, client);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }