Migrate TLS 1.2 and below state machines to the new style.

Bug: 128
Change-Id: Ief3779b1c43dd34a154a0f1d2f94d0da756bc07a
Reviewed-on: https://boringssl-review.googlesource.com/19144
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/ssl/handshake_client.cc b/ssl/handshake_client.cc
index f018c0e..b1c48d8 100644
--- a/ssl/handshake_client.cc
+++ b/ssl/handshake_client.cc
@@ -4,21 +4,21 @@
  * This package is an SSL implementation written
  * by Eric Young (eay@cryptsoft.com).
  * The implementation was written so as to conform with Netscapes SSL.
- * 
+ *
  * This library is free for commercial and non-commercial use as long as
  * the following conditions are aheared to.  The following conditions
  * apply to all code found in this distribution, be it the RC4, RSA,
  * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
  * included with this distribution is covered by the same copyright terms
  * except that the holder is Tim Hudson (tjh@cryptsoft.com).
- * 
+ *
  * Copyright remains Eric Young's, and as such any Copyright notices in
  * the code are not to be removed.
  * If this package is used in a product, Eric Young should be given attribution
  * as the author of the parts of the library used.
  * This can be in the form of a textual message at program startup or
  * in documentation (online or textual) provided with the package.
- * 
+ *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
@@ -33,10 +33,10 @@
  *     Eric Young (eay@cryptsoft.com)"
  *    The word 'cryptographic' can be left out if the rouines from the library
  *    being used are not cryptographic related :-).
- * 4. If you include any Windows specific code (or a derivative thereof) from 
+ * 4. If you include any Windows specific code (or a derivative thereof) from
  *    the apps directory (application code) you must include an acknowledgement:
  *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
- * 
+ *
  * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -48,7 +48,7 @@
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
- * 
+ *
  * The licence and distribution terms for any publically available version or
  * derivative of this code cannot be changed.  i.e. this code cannot simply be
  * copied and put under another distribution licence
@@ -62,7 +62,7 @@
  * are met:
  *
  * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer. 
+ *    notice, this list of conditions and the following disclaimer.
  *
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in
@@ -110,7 +110,7 @@
 /* ====================================================================
  * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED.
  *
- * Portions of the attached software ("Contribution") are developed by 
+ * Portions of the attached software ("Contribution") are developed by
  * SUN MICROSYSTEMS, INC., and are contributed to the OpenSSL project.
  *
  * The Contribution is licensed pursuant to the OpenSSL open source
@@ -150,6 +150,7 @@
 #include <openssl/ssl.h>
 
 #include <assert.h>
+#include <limits.h>
 #include <string.h>
 
 #include <utility>
@@ -172,377 +173,32 @@
 
 namespace bssl {
 
-static int ssl3_send_client_hello(SSL_HANDSHAKE *hs);
-static int dtls1_get_hello_verify_request(SSL_HANDSHAKE *hs);
-static int ssl3_get_server_hello(SSL_HANDSHAKE *hs);
-static int ssl3_get_server_certificate(SSL_HANDSHAKE *hs);
-static int ssl3_get_cert_status(SSL_HANDSHAKE *hs);
-static int ssl3_get_server_key_exchange(SSL_HANDSHAKE *hs);
-static int ssl3_get_certificate_request(SSL_HANDSHAKE *hs);
-static int ssl3_get_server_hello_done(SSL_HANDSHAKE *hs);
-static int ssl3_send_client_certificate(SSL_HANDSHAKE *hs);
-static int ssl3_send_client_key_exchange(SSL_HANDSHAKE *hs);
-static int ssl3_send_cert_verify(SSL_HANDSHAKE *hs);
-static int ssl3_send_next_proto(SSL_HANDSHAKE *hs);
-static int ssl3_send_channel_id(SSL_HANDSHAKE *hs);
-static int ssl3_get_new_session_ticket(SSL_HANDSHAKE *hs);
-
-int ssl3_connect(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
-  int ret = -1;
-
-  assert(ssl->handshake_func == ssl3_connect);
-  assert(!ssl->server);
-
-  for (;;) {
-    int state = hs->state;
-
-    switch (hs->state) {
-      case SSL_ST_INIT:
-        ssl_do_info_callback(ssl, SSL_CB_HANDSHAKE_START, 1);
-        hs->state = SSL3_ST_CW_CLNT_HELLO_A;
-        break;
-
-      case SSL3_ST_CW_CLNT_HELLO_A:
-        ret = ssl3_send_client_hello(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-
-        if (!SSL_is_dtls(ssl) || ssl->d1->send_cookie) {
-          if (hs->early_data_offered) {
-            if (!tls13_init_early_key_schedule(hs) ||
-                !tls13_advance_key_schedule(hs, ssl->session->master_key,
-                                            ssl->session->master_key_length) ||
-                !tls13_derive_early_secrets(hs) ||
-                !tls13_set_traffic_key(ssl, evp_aead_seal,
-                                       hs->early_traffic_secret,
-                                       hs->hash_len)) {
-              ret = -1;
-              goto end;
-            }
-            hs->next_state = SSL3_ST_WRITE_EARLY_DATA;
-          } else {
-            hs->next_state = SSL3_ST_CR_SRVR_HELLO_A;
-          }
-        } else {
-          hs->next_state = DTLS1_ST_CR_HELLO_VERIFY_REQUEST_A;
-        }
-        hs->state = SSL3_ST_CW_FLUSH;
-        break;
-
-      case DTLS1_ST_CR_HELLO_VERIFY_REQUEST_A:
-        assert(SSL_is_dtls(ssl));
-        ret = dtls1_get_hello_verify_request(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        if (ssl->d1->send_cookie) {
-          hs->state = SSL3_ST_CW_CLNT_HELLO_A;
-        } else {
-          hs->state = SSL3_ST_CR_SRVR_HELLO_A;
-        }
-        break;
-
-      case SSL3_ST_WRITE_EARLY_DATA:
-        /* Stash the early data session, so connection properties may be queried
-         * out of it. */
-        hs->in_early_data = 1;
-        SSL_SESSION_up_ref(ssl->session);
-        hs->early_session.reset(ssl->session);
-
-        hs->state = SSL3_ST_CR_SRVR_HELLO_A;
-        hs->can_early_write = 1;
-        ret = 1;
-        goto end;
-
-      case SSL3_ST_CR_SRVR_HELLO_A:
-        ret = ssl3_get_server_hello(hs);
-        if (hs->state == SSL_ST_TLS13) {
-          break;
-        }
-        if (ret <= 0) {
-          goto end;
-        }
-
-        if (ssl->session != NULL) {
-          hs->state = SSL3_ST_CR_SESSION_TICKET_A;
-        } else {
-          hs->state = SSL3_ST_CR_CERT_A;
-        }
-        break;
-
-      case SSL3_ST_CR_CERT_A:
-        if (ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
-          ret = ssl3_get_server_certificate(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_CR_CERT_STATUS_A;
-        break;
-
-      case SSL3_ST_CR_CERT_STATUS_A:
-        if (hs->certificate_status_expected) {
-          ret = ssl3_get_cert_status(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_VERIFY_SERVER_CERT;
-        break;
-
-      case SSL3_ST_VERIFY_SERVER_CERT:
-        if (ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
-          switch (ssl_verify_peer_cert(hs)) {
-            case ssl_verify_ok:
-              break;
-            case ssl_verify_invalid:
-              ret = -1;
-              goto end;
-            case ssl_verify_retry:
-              ssl->rwstate = SSL_CERTIFICATE_VERIFY;
-              ret = -1;
-              goto end;
-          }
-        }
-        hs->state = SSL3_ST_CR_KEY_EXCH_A;
-        break;
-
-      case SSL3_ST_CR_KEY_EXCH_A:
-        ret = ssl3_get_server_key_exchange(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        hs->state = SSL3_ST_CR_CERT_REQ_A;
-        break;
-
-      case SSL3_ST_CR_CERT_REQ_A:
-        if (ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
-          ret = ssl3_get_certificate_request(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_CR_SRVR_DONE_A;
-        break;
-
-      case SSL3_ST_CR_SRVR_DONE_A:
-        ret = ssl3_get_server_hello_done(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        hs->state = SSL3_ST_CW_CERT_A;
-        break;
-
-      case SSL3_ST_CW_CERT_A:
-        if (hs->cert_request) {
-          ret = ssl3_send_client_certificate(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_CW_KEY_EXCH_A;
-        break;
-
-      case SSL3_ST_CW_KEY_EXCH_A:
-        ret = ssl3_send_client_key_exchange(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        hs->state = SSL3_ST_CW_CERT_VRFY_A;
-        break;
-
-      case SSL3_ST_CW_CERT_VRFY_A:
-        if (hs->cert_request && ssl_has_certificate(ssl)) {
-          ret = ssl3_send_cert_verify(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_CW_CHANGE;
-        break;
-
-      case SSL3_ST_CW_CHANGE:
-        if (!ssl->method->add_change_cipher_spec(ssl) ||
-            !tls1_change_cipher_state(hs, SSL3_CHANGE_CIPHER_CLIENT_WRITE)) {
-          ret = -1;
-          goto end;
-        }
-
-        hs->state = SSL3_ST_CW_NEXT_PROTO_A;
-        break;
-
-      case SSL3_ST_CW_NEXT_PROTO_A:
-        if (hs->next_proto_neg_seen) {
-          ret = ssl3_send_next_proto(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_CW_CHANNEL_ID_A;
-        break;
-
-      case SSL3_ST_CW_CHANNEL_ID_A:
-        if (ssl->s3->tlsext_channel_id_valid) {
-          ret = ssl3_send_channel_id(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_CW_FINISHED_A;
-        break;
-
-      case SSL3_ST_CW_FINISHED_A:
-        ret = ssl3_send_finished(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-        hs->state = SSL3_ST_CW_FLUSH;
-
-        if (ssl->session != NULL) {
-          hs->next_state = SSL3_ST_FINISH_CLIENT_HANDSHAKE;
-        } else {
-          /* This is a non-resumption handshake. If it involves ChannelID, then
-           * record the handshake hashes at this point in the session so that
-           * any resumption of this session with ChannelID can sign those
-           * hashes. */
-          ret = tls1_record_handshake_hashes_for_channel_id(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-          if ((SSL_get_mode(ssl) & SSL_MODE_ENABLE_FALSE_START) &&
-              ssl3_can_false_start(ssl) &&
-              /* No False Start on renegotiation (would complicate the state
-               * machine). */
-              !ssl->s3->initial_handshake_complete) {
-            hs->next_state = SSL3_ST_FALSE_START;
-          } else {
-            hs->next_state = SSL3_ST_CR_SESSION_TICKET_A;
-          }
-        }
-        break;
-
-      case SSL3_ST_FALSE_START:
-        hs->state = SSL3_ST_CR_SESSION_TICKET_A;
-        hs->in_false_start = 1;
-        hs->can_early_write = 1;
-        ret = 1;
-        goto end;
-
-      case SSL3_ST_CR_SESSION_TICKET_A:
-        if (hs->ticket_expected) {
-          ret = ssl3_get_new_session_ticket(hs);
-          if (ret <= 0) {
-            goto end;
-          }
-        }
-        hs->state = SSL3_ST_CR_CHANGE;
-        break;
-
-      case SSL3_ST_CR_CHANGE:
-        ret = ssl->method->read_change_cipher_spec(ssl);
-        if (ret <= 0) {
-          goto end;
-        }
-
-        if (!tls1_change_cipher_state(hs, SSL3_CHANGE_CIPHER_CLIENT_READ)) {
-          ret = -1;
-          goto end;
-        }
-        hs->state = SSL3_ST_CR_FINISHED_A;
-        break;
-
-      case SSL3_ST_CR_FINISHED_A:
-        ret = ssl3_get_finished(hs);
-        if (ret <= 0) {
-          goto end;
-        }
-
-        if (ssl->session != NULL) {
-          hs->state = SSL3_ST_CW_CHANGE;
-        } else {
-          hs->state = SSL3_ST_FINISH_CLIENT_HANDSHAKE;
-        }
-        break;
-
-      case SSL3_ST_CW_FLUSH:
-        ret = ssl->method->flush_flight(ssl);
-        if (ret <= 0) {
-          goto end;
-        }
-        hs->state = hs->next_state;
-        break;
-
-      case SSL_ST_TLS13: {
-        int early_return = 0;
-        ret = tls13_handshake(hs, &early_return);
-        if (ret <= 0) {
-          goto end;
-        }
-
-        if (early_return) {
-          ret = 1;
-          goto end;
-        }
-
-        hs->state = SSL3_ST_FINISH_CLIENT_HANDSHAKE;
-        break;
-      }
-
-      case SSL3_ST_FINISH_CLIENT_HANDSHAKE:
-        ssl->method->on_handshake_complete(ssl);
-
-        SSL_SESSION_free(ssl->s3->established_session);
-        if (ssl->session != NULL) {
-          SSL_SESSION_up_ref(ssl->session);
-          ssl->s3->established_session = ssl->session;
-        } else {
-          /* We make a copy of the session in order to maintain the immutability
-           * of the new established_session due to False Start. The caller may
-           * have taken a reference to the temporary session. */
-          ssl->s3->established_session =
-              SSL_SESSION_dup(hs->new_session.get(), SSL_SESSION_DUP_ALL)
-                  .release();
-          if (ssl->s3->established_session == NULL) {
-            ret = -1;
-            goto end;
-          }
-          /* Renegotiations do not participate in session resumption. */
-          if (!ssl->s3->initial_handshake_complete) {
-            ssl->s3->established_session->not_resumable = 0;
-          }
-
-          hs->new_session.reset();
-        }
-
-        hs->state = SSL_ST_OK;
-        break;
-
-      case SSL_ST_OK: {
-        ssl->s3->initial_handshake_complete = 1;
-        ssl_update_cache(hs, SSL_SESS_CACHE_CLIENT);
-
-        ret = 1;
-        ssl_do_info_callback(ssl, SSL_CB_HANDSHAKE_DONE, 1);
-        goto end;
-      }
-
-      default:
-        OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_STATE);
-        ret = -1;
-        goto end;
-    }
-
-    if (hs->state != state) {
-      ssl_do_info_callback(ssl, SSL_CB_CONNECT_LOOP, 1);
-    }
-  }
-
-end:
-  ssl_do_info_callback(ssl, SSL_CB_CONNECT_EXIT, ret);
-  return ret;
-}
+enum ssl_client_hs_state_t {
+  state_start_connect = 0,
+  state_send_client_hello,
+  state_enter_early_data,
+  state_read_hello_verify_request,
+  state_read_server_hello,
+  state_tls13,
+  state_read_server_certificate,
+  state_read_certificate_status,
+  state_verify_server_certificate,
+  state_read_server_key_exchange,
+  state_read_certificate_request,
+  state_read_server_hello_done,
+  state_send_client_certificate,
+  state_send_client_key_exchange,
+  state_send_client_certificate_verify,
+  state_send_second_client_flight,
+  state_send_channel_id,
+  state_send_client_finished,
+  state_finish_flight,
+  state_read_session_ticket,
+  state_process_change_cipher_spec,
+  state_read_server_finished,
+  state_finish_client_handshake,
+  state_done,
+};
 
 /* ssl_get_client_disabled sets |*out_mask_a| and |*out_mask_k| to masks of
  * disabled algorithms. */
@@ -702,99 +358,6 @@
   return ssl->method->add_message(ssl, msg, len);
 }
 
-static int ssl3_send_client_hello(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
-  /* The handshake buffer is reset on every ClientHello. Notably, in DTLS, we
-   * may send multiple ClientHellos if we receive HelloVerifyRequest. */
-  if (!hs->transcript.Init()) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
-  }
-
-  /* Freeze the version range. */
-  if (!ssl_get_version_range(ssl, &hs->min_version, &hs->max_version)) {
-    return -1;
-  }
-
-  /* Always advertise the ClientHello version from the original maximum version,
-   * even on renegotiation. The static RSA key exchange uses this field, and
-   * some servers fail when it changes across handshakes. */
-  if (SSL_is_dtls(hs->ssl)) {
-    hs->client_version =
-        hs->max_version >= TLS1_2_VERSION ? DTLS1_2_VERSION : DTLS1_VERSION;
-  } else {
-    hs->client_version =
-        hs->max_version >= TLS1_2_VERSION ? TLS1_2_VERSION : hs->max_version;
-  }
-
-  /* If the configured session has expired or was created at a disabled
-   * version, drop it. */
-  if (ssl->session != NULL) {
-    if (ssl->session->is_server ||
-        !ssl_supports_version(hs, ssl->session->ssl_version) ||
-        (ssl->session->session_id_length == 0 &&
-         ssl->session->tlsext_ticklen == 0) ||
-        ssl->session->not_resumable ||
-        !ssl_session_is_time_valid(ssl, ssl->session)) {
-      ssl_set_session(ssl, NULL);
-    }
-  }
-
-  /* If resending the ClientHello in DTLS after a HelloVerifyRequest, don't
-   * renegerate the client_random. The random must be reused. */
-  if ((!SSL_is_dtls(ssl) || !ssl->d1->send_cookie) &&
-      !RAND_bytes(ssl->s3->client_random, sizeof(ssl->s3->client_random))) {
-    return -1;
-  }
-
-  /* Initialize a random session ID for the experimental TLS 1.3 variant
-   * requiring a session id. */
-  if (ssl->tls13_variant == tls13_experiment) {
-    hs->session_id_len = sizeof(hs->session_id);
-    if (!RAND_bytes(hs->session_id, hs->session_id_len)) {
-      return -1;
-    }
-  }
-
-  if (!ssl_write_client_hello(hs)) {
-    return -1;
-  }
-
-  return 1;
-}
-
-static int dtls1_get_hello_verify_request(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
-  SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
-  }
-
-  if (msg.type != DTLS1_MT_HELLO_VERIFY_REQUEST) {
-    ssl->d1->send_cookie = false;
-    return 1;
-  }
-
-  CBS hello_verify_request = msg.body, cookie;
-  uint16_t server_version;
-  if (!CBS_get_u16(&hello_verify_request, &server_version) ||
-      !CBS_get_u8_length_prefixed(&hello_verify_request, &cookie) ||
-      CBS_len(&cookie) > sizeof(ssl->d1->cookie) ||
-      CBS_len(&hello_verify_request) != 0) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return -1;
-  }
-
-  OPENSSL_memcpy(ssl->d1->cookie, CBS_data(&cookie), CBS_len(&cookie));
-  ssl->d1->cookie_len = CBS_len(&cookie);
-
-  ssl->d1->send_cookie = true;
-  ssl->method->next_message(ssl);
-  return 1;
-}
-
 static int parse_server_version(SSL_HANDSHAKE *hs, uint16_t *out,
                                 const SSLMessage &msg) {
   SSL *const ssl = hs->ssl;
@@ -866,34 +429,160 @@
   return 1;
 }
 
-static int ssl3_get_server_hello(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_start_connect(SSL_HANDSHAKE *hs) {
+  ssl_do_info_callback(hs->ssl, SSL_CB_HANDSHAKE_START, 1);
+  hs->state = state_send_client_hello;
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_send_client_hello(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+
+  /* The handshake buffer is reset on every ClientHello. Notably, in DTLS, we
+   * may send multiple ClientHellos if we receive HelloVerifyRequest. */
+  if (!hs->transcript.Init()) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return ssl_hs_error;
+  }
+
+  /* Freeze the version range. */
+  if (!ssl_get_version_range(ssl, &hs->min_version, &hs->max_version)) {
+    return ssl_hs_error;
+  }
+
+  /* Always advertise the ClientHello version from the original maximum version,
+   * even on renegotiation. The static RSA key exchange uses this field, and
+   * some servers fail when it changes across handshakes. */
+  if (SSL_is_dtls(hs->ssl)) {
+    hs->client_version =
+        hs->max_version >= TLS1_2_VERSION ? DTLS1_2_VERSION : DTLS1_VERSION;
+  } else {
+    hs->client_version =
+        hs->max_version >= TLS1_2_VERSION ? TLS1_2_VERSION : hs->max_version;
+  }
+
+  /* If the configured session has expired or was created at a disabled
+   * version, drop it. */
+  if (ssl->session != NULL) {
+    if (ssl->session->is_server ||
+        !ssl_supports_version(hs, ssl->session->ssl_version) ||
+        (ssl->session->session_id_length == 0 &&
+         ssl->session->tlsext_ticklen == 0) ||
+        ssl->session->not_resumable ||
+        !ssl_session_is_time_valid(ssl, ssl->session)) {
+      ssl_set_session(ssl, NULL);
+    }
+  }
+
+  /* If resending the ClientHello in DTLS after a HelloVerifyRequest, don't
+   * renegerate the client_random. The random must be reused. */
+  if ((!SSL_is_dtls(ssl) || !ssl->d1->send_cookie) &&
+      !RAND_bytes(ssl->s3->client_random, sizeof(ssl->s3->client_random))) {
+    return ssl_hs_error;
+  }
+
+  /* Initialize a random session ID for the experimental TLS 1.3 variant
+   * requiring a session id. */
+  if (ssl->tls13_variant == tls13_experiment) {
+    hs->session_id_len = sizeof(hs->session_id);
+    if (!RAND_bytes(hs->session_id, hs->session_id_len)) {
+      return ssl_hs_error;
+    }
+  }
+
+  if (!ssl_write_client_hello(hs)) {
+    return ssl_hs_error;
+  }
+
+  hs->state = state_enter_early_data;
+  return ssl_hs_flush;
+}
+
+static enum ssl_hs_wait_t do_enter_early_data(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+
+  if (SSL_is_dtls(ssl) && !ssl->d1->send_cookie) {
+    hs->state = state_read_hello_verify_request;
+    return ssl_hs_ok;
+  }
+
+  if (!hs->early_data_offered) {
+    hs->state = state_read_server_hello;
+    return ssl_hs_ok;
+  }
+
+  if (!tls13_init_early_key_schedule(hs) ||
+      !tls13_advance_key_schedule(hs, ssl->session->master_key,
+                                  ssl->session->master_key_length) ||
+      !tls13_derive_early_secrets(hs) ||
+      !tls13_set_traffic_key(ssl, evp_aead_seal, hs->early_traffic_secret,
+                             hs->hash_len)) {
+    return ssl_hs_error;
+  }
+
+  /* Stash the early data session, so connection properties may be queried out
+   * of it. */
+  hs->in_early_data = 1;
+  SSL_SESSION_up_ref(ssl->session);
+  hs->early_session.reset(ssl->session);
+  hs->can_early_write = 1;
+
+  hs->state = state_read_server_hello;
+  return ssl_hs_early_return;
+}
+
+static enum ssl_hs_wait_t do_read_hello_verify_request(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+
+  assert(SSL_is_dtls(ssl));
+
+  SSLMessage msg;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
+  }
+
+  if (msg.type != DTLS1_MT_HELLO_VERIFY_REQUEST) {
+    ssl->d1->send_cookie = false;
+    hs->state = state_read_server_hello;
+    return ssl_hs_ok;
+  }
+
+  CBS hello_verify_request = msg.body, cookie;
+  uint16_t server_version;
+  if (!CBS_get_u16(&hello_verify_request, &server_version) ||
+      !CBS_get_u8_length_prefixed(&hello_verify_request, &cookie) ||
+      CBS_len(&cookie) > sizeof(ssl->d1->cookie) ||
+      CBS_len(&hello_verify_request) != 0) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+    ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+    return ssl_hs_error;
+  }
+
+  OPENSSL_memcpy(ssl->d1->cookie, CBS_data(&cookie), CBS_len(&cookie));
+  ssl->d1->cookie_len = CBS_len(&cookie);
+
+  ssl->d1->send_cookie = true;
+  ssl->method->next_message(ssl);
+  hs->state = state_send_client_hello;
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_read_server_hello(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    uint32_t err = ERR_peek_error();
-    if (ERR_GET_LIB(err) == ERR_LIB_SSL &&
-        ERR_GET_REASON(err) == SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE) {
-      /* Add a dedicated error code to the queue for a handshake_failure alert
-       * in response to ClientHello. This matches NSS's client behavior and
-       * gives a better error on a (probable) failure to negotiate initial
-       * parameters. Note: this error code comes after the original one.
-       *
-       * See https://crbug.com/446505. */
-      OPENSSL_PUT_ERROR(SSL, SSL_R_HANDSHAKE_FAILURE_ON_CLIENT_HELLO);
-    }
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_server_hello;
   }
 
   uint16_t server_version;
   if (!parse_server_version(hs, &server_version, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   if (!ssl_supports_version(hs, server_version)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_PROTOCOL);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_PROTOCOL_VERSION);
-    return -1;
+    return ssl_hs_error;
   }
 
   assert(ssl->s3->have_version == ssl->s3->initial_handshake_complete);
@@ -905,25 +594,24 @@
   } else if (server_version != ssl->version) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SSL_VERSION);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_PROTOCOL_VERSION);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (ssl3_protocol_version(ssl) >= TLS1_3_VERSION) {
-    hs->state = SSL_ST_TLS13;
-    hs->do_tls13_handshake = tls13_client_handshake;
-    return 1;
+    hs->state = state_tls13;
+    return ssl_hs_ok;
   }
 
   if (hs->early_data_offered) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_VERSION_ON_EARLY_DATA);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_PROTOCOL_VERSION);
-    return -1;
+    return ssl_hs_error;
   }
 
   ssl_clear_tls13_state(hs);
 
   if (!ssl_check_message_type(ssl, msg, SSL3_MT_SERVER_HELLO)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   CBS server_hello = msg.body, server_random, session_id;
@@ -937,7 +625,7 @@
       !CBS_get_u8(&server_hello, &compression_method)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* Copy over the server random. */
@@ -958,7 +646,7 @@
     ssl_set_session(ssl, NULL);
     if (!ssl_get_new_session(hs, 0 /* client */)) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
     /* Note: session_id could be empty. */
     hs->new_session->session_id_length = CBS_len(&session_id);
@@ -971,7 +659,7 @@
     /* unknown cipher */
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CIPHER_RETURNED);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* The cipher must be allowed in the selected version and enabled. */
@@ -983,26 +671,26 @@
       !sk_SSL_CIPHER_find(SSL_get_ciphers(ssl), NULL, cipher)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CIPHER_RETURNED);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (ssl->session != NULL) {
     if (ssl->session->ssl_version != ssl->version) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_VERSION_NOT_RETURNED);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-      return -1;
+      return ssl_hs_error;
     }
     if (ssl->session->cipher != cipher) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_CIPHER_NOT_RETURNED);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-      return -1;
+      return ssl_hs_error;
     }
     if (!ssl_session_is_context_valid(ssl, ssl->session)) {
       /* This is actually a client application bug. */
       OPENSSL_PUT_ERROR(SSL,
                         SSL_R_ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-      return -1;
+      return ssl_hs_error;
     }
   } else {
     hs->new_session->cipher = cipher;
@@ -1014,7 +702,7 @@
   if (!hs->transcript.InitHash(ssl3_protocol_version(ssl), hs->new_cipher) ||
       !ssl_hash_message(hs, msg)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* If doing a full handshake, the server may request a client certificate
@@ -1029,13 +717,13 @@
   if (compression_method != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_COMPRESSION_ALGORITHM);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* TLS extensions */
   if (!ssl_parse_serverhello_tlsext(hs, &server_hello)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_PARSE_TLSEXT);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* There should be nothing left over in the record. */
@@ -1043,7 +731,7 @@
     /* wrong packet length */
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (ssl->session != NULL &&
@@ -1054,24 +742,46 @@
       OPENSSL_PUT_ERROR(SSL, SSL_R_RESUMED_NON_EMS_SESSION_WITH_EMS_EXTENSION);
     }
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-    return -1;
+    return ssl_hs_error;
   }
 
   ssl->method->next_message(ssl);
-  return 1;
+
+  if (ssl->session != NULL) {
+    hs->state = state_read_session_ticket;
+    return ssl_hs_ok;
+  }
+
+  hs->state = state_read_server_certificate;
+  return ssl_hs_ok;
 }
 
-static int ssl3_get_server_certificate(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_tls13(SSL_HANDSHAKE *hs) {
+  enum ssl_hs_wait_t wait = tls13_client_handshake(hs);
+  if (wait == ssl_hs_ok) {
+    hs->state = state_finish_client_handshake;
+    return ssl_hs_ok;
+  }
+
+  return wait;
+}
+
+static enum ssl_hs_wait_t do_read_server_certificate(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
+  if (!ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
+    hs->state = state_read_certificate_status;
+    return ssl_hs_ok;
+  }
+
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (!ssl_check_message_type(ssl, msg, SSL3_MT_CERTIFICATE) ||
       !ssl_hash_message(hs, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   CBS body = msg.body;
@@ -1080,7 +790,7 @@
   if (!ssl_parse_cert_chain(&alert, &chain, &hs->peer_pubkey, NULL, &body,
                             ssl->ctx->pool)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
-    return -1;
+    return ssl_hs_error;
   }
   sk_CRYPTO_BUFFER_pop_free(hs->new_session->certs, CRYPTO_BUFFER_free);
   hs->new_session->certs = chain.release();
@@ -1090,36 +800,44 @@
       !ssl->ctx->x509_method->session_cache_objects(hs->new_session.get())) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (!ssl_check_leaf_certificate(
           hs, hs->peer_pubkey.get(),
           sk_CRYPTO_BUFFER_value(hs->new_session->certs, 0))) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-    return -1;
+    return ssl_hs_error;
   }
 
   ssl->method->next_message(ssl);
-  return 1;
+
+  hs->state = state_read_certificate_status;
+  return ssl_hs_ok;
 }
 
-static int ssl3_get_cert_status(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_read_certificate_status(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
+  if (!hs->certificate_status_expected) {
+    hs->state = state_verify_server_certificate;
+    return ssl_hs_ok;
+  }
+
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (msg.type != SSL3_MT_CERTIFICATE_STATUS) {
     /* A server may send status_request in ServerHello and then change
      * its mind about sending CertificateStatus. */
-    return 1;
+    hs->state = state_verify_server_certificate;
+    return ssl_hs_ok;
   }
 
   if (!ssl_hash_message(hs, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   CBS certificate_status = msg.body, ocsp_response;
@@ -1131,7 +849,7 @@
       CBS_len(&certificate_status) != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   CRYPTO_BUFFER_free(hs->new_session->ocsp_response);
@@ -1139,19 +857,40 @@
       CRYPTO_BUFFER_new_from_CBS(&ocsp_response, ssl->ctx->pool);
   if (hs->new_session->ocsp_response == nullptr) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   ssl->method->next_message(ssl);
-  return 1;
+
+  hs->state = state_verify_server_certificate;
+  return ssl_hs_ok;
 }
 
-static int ssl3_get_server_key_exchange(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_verify_server_certificate(SSL_HANDSHAKE *hs) {
+  if (!ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
+    hs->state = state_read_server_key_exchange;
+    return ssl_hs_ok;
+  }
+
+  switch (ssl_verify_peer_cert(hs)) {
+    case ssl_verify_ok:
+      break;
+    case ssl_verify_invalid:
+      return ssl_hs_error;
+    case ssl_verify_retry:
+      hs->state = state_verify_server_certificate;
+      return ssl_hs_certificate_verify;
+  }
+
+  hs->state = state_read_server_key_exchange;
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_read_server_key_exchange(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (msg.type != SSL3_MT_SERVER_KEY_EXCHANGE) {
@@ -1159,14 +898,15 @@
     if (ssl_cipher_requires_server_key_exchange(hs->new_cipher)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_MESSAGE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
-      return -1;
+      return ssl_hs_error;
     }
 
-    return 1;
+    hs->state = state_read_certificate_request;
+    return ssl_hs_ok;
   }
 
   if (!ssl_hash_message(hs, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   uint32_t alg_k = hs->new_cipher->algorithm_mkey;
@@ -1180,7 +920,7 @@
                                      &psk_identity_hint)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
 
     /* Store PSK identity hint for later use, hint is used in
@@ -1194,7 +934,7 @@
         CBS_contains_zero_byte(&psk_identity_hint)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DATA_LENGTH_TOO_LONG);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-      return -1;
+      return ssl_hs_error;
     }
 
     /* Save non-empty identity hints as a C string. Empty identity hints we
@@ -1207,7 +947,7 @@
         !CBS_strdup(&psk_identity_hint, &raw)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
     hs->peer_psk_identity_hint.reset(raw);
   }
@@ -1223,7 +963,7 @@
         !CBS_get_u8_length_prefixed(&server_key_exchange, &point)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
     hs->new_session->group_id = group_id;
 
@@ -1231,19 +971,19 @@
     if (!tls1_check_group_id(ssl, group_id)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CURVE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-      return -1;
+      return ssl_hs_error;
     }
 
     /* Initialize ECDH and save the peer public key for later. */
     hs->key_share = SSLKeyShare::Create(group_id);
     if (!hs->key_share ||
         !CBS_stow(&point, &hs->peer_key, &hs->peer_key_len)) {
-      return -1;
+      return ssl_hs_error;
     }
   } else if (!(alg_k & SSL_kPSK)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_MESSAGE);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
-    return -1;
+    return ssl_hs_error;
   }
 
   /* At this point, |server_key_exchange| contains the signature, if any, while
@@ -1260,19 +1000,19 @@
       if (!CBS_get_u16(&server_key_exchange, &signature_algorithm)) {
         OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-        return -1;
+        return ssl_hs_error;
       }
       uint8_t alert = SSL_AD_DECODE_ERROR;
       if (!tls12_check_peer_sigalg(ssl, &alert, signature_algorithm)) {
         ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
-        return -1;
+        return ssl_hs_error;
       }
       hs->new_session->peer_signature_algorithm = signature_algorithm;
     } else if (!tls1_get_legacy_signature_algorithm(&signature_algorithm,
                                                     hs->peer_pubkey.get())) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_ERROR_UNSUPPORTED_CERTIFICATE_TYPE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_CERTIFICATE);
-      return -1;
+      return ssl_hs_error;
     }
 
     /* The last field in |server_key_exchange| is the signature. */
@@ -1281,7 +1021,7 @@
         CBS_len(&server_key_exchange) != 0) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
 
     ScopedCBB transcript;
@@ -1298,7 +1038,7 @@
         !CBB_finish(transcript.get(), &transcript_data, &transcript_len)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
 
     int sig_ok = ssl_public_key_verify(
@@ -1314,7 +1054,7 @@
       /* bad signature */
       OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SIGNATURE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
   } else {
     /* PSK ciphers are the only supported certificate-less ciphers. */
@@ -1323,32 +1063,39 @@
     if (CBS_len(&server_key_exchange) > 0) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_EXTRA_DATA_IN_MESSAGE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
   }
 
   ssl->method->next_message(ssl);
-  return 1;
+  hs->state = state_read_certificate_request;
+  return ssl_hs_ok;
 }
 
-static int ssl3_get_certificate_request(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_read_certificate_request(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
+  if (!ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
+    hs->state = state_read_server_hello_done;
+    return ssl_hs_ok;
+  }
+
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (msg.type == SSL3_MT_SERVER_HELLO_DONE) {
     /* If we get here we don't need the handshake buffer as we won't be doing
      * client auth. */
     hs->transcript.FreeBuffer();
-    return 1;
+    hs->state = state_read_server_hello_done;
+    return ssl_hs_ok;
   }
 
   if (!ssl_check_message_type(ssl, msg, SSL3_MT_CERTIFICATE_REQUEST) ||
       !ssl_hash_message(hs, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   /* Get the certificate types. */
@@ -1356,13 +1103,13 @@
   if (!CBS_get_u8_length_prefixed(&body, &certificate_types)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (!CBS_stow(&certificate_types, &hs->certificate_types,
                 &hs->num_certificate_types)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (ssl3_protocol_version(ssl) >= TLS1_2_VERSION) {
@@ -1371,7 +1118,7 @@
         !tls1_parse_peer_sigalgs(hs, &supported_signature_algorithms)) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
   }
 
@@ -1380,59 +1127,68 @@
       ssl_parse_client_CA_list(ssl, &alert, &body);
   if (!ca_names) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (CBS_len(&body) != 0) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   hs->cert_request = 1;
   hs->ca_names = std::move(ca_names);
   ssl->ctx->x509_method->hs_flush_cached_ca_names(hs);
+
   ssl->method->next_message(ssl);
-  return 1;
+  hs->state = state_read_server_hello_done;
+  return ssl_hs_ok;
 }
 
-static int ssl3_get_server_hello_done(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_read_server_hello_done(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (!ssl_check_message_type(ssl, msg, SSL3_MT_SERVER_HELLO_DONE) ||
       !ssl_hash_message(hs, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   /* ServerHelloDone is empty. */
   if (CBS_len(&msg.body) != 0) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   ssl->method->next_message(ssl);
-  return 1;
+  hs->state = state_send_client_certificate;
+  return ssl_hs_ok;
 }
 
-static int ssl3_send_client_certificate(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_send_client_certificate(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
+  /* The peer didn't request a certificate. */
+  if (!hs->cert_request) {
+    hs->state = state_send_client_key_exchange;
+    return ssl_hs_ok;
+  }
+
   /* Call cert_cb to update the certificate. */
-  if (ssl->cert->cert_cb) {
-    int ret = ssl->cert->cert_cb(ssl, ssl->cert->cert_cb_arg);
-    if (ret < 0) {
-      ssl->rwstate = SSL_X509_LOOKUP;
-      return -1;
-    }
-    if (ret == 0) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_CB_ERROR);
+  if (ssl->cert->cert_cb != NULL) {
+    int rv = ssl->cert->cert_cb(ssl, ssl->cert->cert_cb_arg);
+    if (rv == 0) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      return -1;
+      OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_CB_ERROR);
+      return ssl_hs_error;
+    }
+    if (rv < 0) {
+      hs->state = state_send_client_certificate;
+      return ssl_hs_x509_lookup;
     }
   }
 
@@ -1444,29 +1200,33 @@
     if (ssl->version == SSL3_VERSION) {
       if (!ssl->method->add_alert(ssl, SSL3_AL_WARNING,
                                   SSL_AD_NO_CERTIFICATE)) {
-        return -1;
+        return ssl_hs_error;
       }
-      return 1;
+      hs->state = state_send_client_key_exchange;
+      return ssl_hs_ok;
     }
   }
 
   if (!ssl_on_certificate_selected(hs) ||
       !ssl3_output_cert_chain(ssl)) {
-    return -1;
+    return ssl_hs_error;
   }
-  return 1;
+
+
+  hs->state = state_send_client_key_exchange;
+  return ssl_hs_ok;
 }
 
 static_assert(sizeof(size_t) >= sizeof(unsigned),
               "size_t is smaller than unsigned");
 
-static int ssl3_send_client_key_exchange(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_send_client_key_exchange(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   ScopedCBB cbb;
   CBB body;
   if (!ssl->method->init_message(ssl, cbb.get(), &body,
                                  SSL3_MT_CLIENT_KEY_EXCHANGE)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   uint8_t *pms = NULL;
@@ -1628,36 +1388,43 @@
   OPENSSL_cleanse(pms, pms_len);
   OPENSSL_free(pms);
 
-  return 1;
+  hs->state = state_send_client_certificate_verify;
+  return ssl_hs_ok;
 
 err:
   if (pms != NULL) {
     OPENSSL_cleanse(pms, pms_len);
     OPENSSL_free(pms);
   }
-  return -1;
+  return ssl_hs_error;
+
 }
 
-static int ssl3_send_cert_verify(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_send_client_certificate_verify(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  assert(ssl_has_private_key(ssl));
 
+  if (!hs->cert_request || !ssl_has_certificate(ssl)) {
+    hs->state = state_send_second_client_flight;
+    return ssl_hs_ok;
+  }
+
+  assert(ssl_has_private_key(ssl));
   ScopedCBB cbb;
   CBB body, child;
   if (!ssl->method->init_message(ssl, cbb.get(), &body,
                                  SSL3_MT_CERTIFICATE_VERIFY)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   uint16_t signature_algorithm;
   if (!tls1_choose_signature_algorithm(hs, &signature_algorithm)) {
-    return -1;
+    return ssl_hs_error;
   }
   if (ssl3_protocol_version(ssl) >= TLS1_2_VERSION) {
     /* Write out the digest type in TLS 1.2. */
     if (!CBB_add_u16(&body, signature_algorithm)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
   }
 
@@ -1666,7 +1433,7 @@
   uint8_t *ptr;
   if (!CBB_add_u16_length_prefixed(&body, &child) ||
       !CBB_reserve(&child, &ptr, max_sig_len)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   size_t sig_len = max_sig_len;
@@ -1675,21 +1442,21 @@
   if (ssl3_protocol_version(ssl) == SSL3_VERSION) {
     if (ssl->cert->key_method != NULL) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_PROTOCOL_FOR_CUSTOM_KEY);
-      return -1;
+      return ssl_hs_error;
     }
 
     uint8_t digest[EVP_MAX_MD_SIZE];
     size_t digest_len;
     if (!hs->transcript.GetSSL3CertVerifyHash(
             digest, &digest_len, hs->new_session.get(), signature_algorithm)) {
-      return -1;
+      return ssl_hs_error;
     }
 
     UniquePtr<EVP_PKEY_CTX> pctx(EVP_PKEY_CTX_new(ssl->cert->privatekey, NULL));
     if (!pctx ||
         !EVP_PKEY_sign_init(pctx.get()) ||
         !EVP_PKEY_sign(pctx.get(), ptr, &sig_len, digest, digest_len)) {
-      return -1;
+      return ssl_hs_error;
     }
   } else {
     switch (ssl_private_key_sign(
@@ -1698,53 +1465,70 @@
       case ssl_private_key_success:
         break;
       case ssl_private_key_failure:
-        return -1;
+        return ssl_hs_error;
       case ssl_private_key_retry:
-        ssl->rwstate = SSL_PRIVATE_KEY_OPERATION;
-        return -1;
+        hs->state = state_send_client_certificate_verify;
+        return ssl_hs_private_key_operation;
     }
   }
 
   if (!CBB_did_write(&child, sig_len) ||
       !ssl_add_message_cbb(ssl, cbb.get())) {
-    return -1;
+    return ssl_hs_error;
   }
 
   /* The handshake buffer is no longer necessary. */
   hs->transcript.FreeBuffer();
-  return 1;
+
+  hs->state = state_send_second_client_flight;
+  return ssl_hs_ok;
 }
 
-static int ssl3_send_next_proto(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_send_second_client_flight(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  static const uint8_t kZero[32] = {0};
-  size_t padding_len = 32 - ((ssl->s3->next_proto_negotiated_len + 2) % 32);
 
-  ScopedCBB cbb;
-  CBB body, child;
-  if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_NEXT_PROTO) ||
-      !CBB_add_u8_length_prefixed(&body, &child) ||
-      !CBB_add_bytes(&child, ssl->s3->next_proto_negotiated,
-                     ssl->s3->next_proto_negotiated_len) ||
-      !CBB_add_u8_length_prefixed(&body, &child) ||
-      !CBB_add_bytes(&child, kZero, padding_len) ||
-      !ssl_add_message_cbb(ssl, cbb.get())) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
+  if (!ssl->method->add_change_cipher_spec(ssl) ||
+      !tls1_change_cipher_state(hs, SSL3_CHANGE_CIPHER_CLIENT_WRITE)) {
+    return ssl_hs_error;
   }
 
-  return 1;
+  if (hs->next_proto_neg_seen) {
+    static const uint8_t kZero[32] = {0};
+    size_t padding_len = 32 - ((ssl->s3->next_proto_negotiated_len + 2) % 32);
+
+    ScopedCBB cbb;
+    CBB body, child;
+    if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_NEXT_PROTO) ||
+        !CBB_add_u8_length_prefixed(&body, &child) ||
+        !CBB_add_bytes(&child, ssl->s3->next_proto_negotiated,
+                       ssl->s3->next_proto_negotiated_len) ||
+        !CBB_add_u8_length_prefixed(&body, &child) ||
+        !CBB_add_bytes(&child, kZero, padding_len) ||
+        !ssl_add_message_cbb(ssl, cbb.get())) {
+      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+      return ssl_hs_error;
+    }
+  }
+
+  hs->state = state_send_channel_id;
+  return ssl_hs_ok;
 }
 
-static int ssl3_send_channel_id(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_send_channel_id(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
+  if (!ssl->s3->tlsext_channel_id_valid) {
+    hs->state = state_send_client_finished;
+    return ssl_hs_ok;
+  }
+
   if (!ssl_do_channel_id_callback(ssl)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   if (ssl->tlsext_channel_id_private == NULL) {
-    ssl->rwstate = SSL_CHANNEL_ID_LOOKUP;
-    return -1;
+    hs->state = state_send_channel_id;
+    return ssl_hs_channel_id_lookup;
   }
 
   ScopedCBB cbb;
@@ -1753,23 +1537,67 @@
       !tls1_write_channel_id(hs, &body) ||
       !ssl_add_message_cbb(ssl, cbb.get())) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
-  return 1;
+  hs->state = state_send_client_finished;
+  return ssl_hs_ok;
 }
 
-static int ssl3_get_new_session_ticket(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_send_client_finished(SSL_HANDSHAKE *hs) {
+  if (!ssl3_send_finished(hs)) {
+    return ssl_hs_error;
+  }
+
+  hs->state = state_finish_flight;
+  return ssl_hs_flush;
+}
+
+static enum ssl_hs_wait_t do_finish_flight(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+  if (ssl->session != NULL) {
+    hs->state = state_finish_client_handshake;
+    return ssl_hs_ok;
+  }
+
+  /* This is a full handshake. If it involves ChannelID, then record the
+   * handshake hashes at this point in the session so that any resumption of
+   * this session with ChannelID can sign those hashes. */
+  if (!tls1_record_handshake_hashes_for_channel_id(hs)) {
+    return ssl_hs_error;
+  }
+
+  hs->state = state_read_session_ticket;
+
+  if ((SSL_get_mode(ssl) & SSL_MODE_ENABLE_FALSE_START) &&
+      ssl3_can_false_start(ssl) &&
+      /* No False Start on renegotiation (would complicate the state
+       * machine). */
+      !ssl->s3->initial_handshake_complete) {
+    hs->in_false_start = 1;
+    hs->can_early_write = 1;
+    return ssl_hs_early_return;
+  }
+
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_read_session_ticket(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+
+  if (!hs->ticket_expected) {
+    hs->state = state_process_change_cipher_spec;
+    return ssl_hs_read_change_cipher_spec;
+  }
+
   SSLMessage msg;
-  int ret = ssl_read_message(ssl, &msg);
-  if (ret <= 0) {
-    return ret;
+  if (!ssl->method->get_message(ssl, &msg)) {
+    return ssl_hs_read_message;
   }
 
   if (!ssl_check_message_type(ssl, msg, SSL3_MT_NEW_SESSION_TICKET) ||
       !ssl_hash_message(hs, msg)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   CBS new_session_ticket = msg.body, ticket;
@@ -1779,7 +1607,7 @@
       CBS_len(&new_session_ticket) != 0) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    return -1;
+    return ssl_hs_error;
   }
 
   if (CBS_len(&ticket) == 0) {
@@ -1788,7 +1616,8 @@
      * |ssl_update_cache| so is cleared here to avoid an unnecessary update. */
     hs->ticket_expected = 0;
     ssl->method->next_message(ssl);
-    return 1;
+    hs->state = state_process_change_cipher_spec;
+    return ssl_hs_read_change_cipher_spec;
   }
 
   SSL_SESSION *session = hs->new_session.get();
@@ -1802,7 +1631,7 @@
     if (!renewed_session) {
       /* This should never happen. */
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return -1;
+      return ssl_hs_error;
     }
     session = renewed_session.get();
   }
@@ -1812,7 +1641,7 @@
 
   if (!CBS_stow(&ticket, &session->tlsext_tick, &session->tlsext_ticklen)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return -1;
+    return ssl_hs_error;
   }
   session->tlsext_tick_lifetime_hint = tlsext_tick_lifetime_hint;
 
@@ -1822,7 +1651,7 @@
   if (!EVP_Digest(CBS_data(&ticket), CBS_len(&ticket),
                   session->session_id, &session->session_id_length,
                   EVP_sha256(), NULL)) {
-    return -1;
+    return ssl_hs_error;
   }
 
   if (renewed_session) {
@@ -1832,7 +1661,217 @@
   }
 
   ssl->method->next_message(ssl);
-  return 1;
+  hs->state = state_process_change_cipher_spec;
+  return ssl_hs_read_change_cipher_spec;
 }
 
-}  // namespace bssl
+static enum ssl_hs_wait_t do_process_change_cipher_spec(SSL_HANDSHAKE *hs) {
+  if (!tls1_change_cipher_state(hs, SSL3_CHANGE_CIPHER_CLIENT_READ)) {
+    return ssl_hs_error;
+  }
+
+  hs->state = state_read_server_finished;
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_read_server_finished(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+  enum ssl_hs_wait_t wait = ssl_get_finished(hs);
+  if (wait != ssl_hs_ok) {
+    return wait;
+  }
+
+  if (ssl->session != NULL) {
+    hs->state = state_send_second_client_flight;
+    return ssl_hs_ok;
+  }
+
+  hs->state = state_finish_client_handshake;
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_finish_client_handshake(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+
+  ssl->method->on_handshake_complete(ssl);
+
+  SSL_SESSION_free(ssl->s3->established_session);
+  if (ssl->session != NULL) {
+    SSL_SESSION_up_ref(ssl->session);
+    ssl->s3->established_session = ssl->session;
+  } else {
+    /* We make a copy of the session in order to maintain the immutability
+     * of the new established_session due to False Start. The caller may
+     * have taken a reference to the temporary session. */
+    ssl->s3->established_session =
+        SSL_SESSION_dup(hs->new_session.get(), SSL_SESSION_DUP_ALL)
+        .release();
+    if (ssl->s3->established_session == NULL) {
+      return ssl_hs_error;
+    }
+    /* Renegotiations do not participate in session resumption. */
+    if (!ssl->s3->initial_handshake_complete) {
+      ssl->s3->established_session->not_resumable = 0;
+    }
+
+    hs->new_session.reset();
+  }
+
+  ssl->s3->initial_handshake_complete = 1;
+  ssl_update_cache(hs, SSL_SESS_CACHE_CLIENT);
+
+  hs->state = state_done;
+  return ssl_hs_ok;
+}
+
+enum ssl_hs_wait_t ssl_client_handshake(SSL_HANDSHAKE *hs) {
+  while (hs->state != state_done) {
+    enum ssl_hs_wait_t ret = ssl_hs_error;
+    enum ssl_client_hs_state_t state =
+        static_cast<enum ssl_client_hs_state_t>(hs->state);
+    switch (state) {
+      case state_start_connect:
+        ret = do_start_connect(hs);
+        break;
+      case state_send_client_hello:
+        ret = do_send_client_hello(hs);
+        break;
+      case state_enter_early_data:
+        ret = do_enter_early_data(hs);
+        break;
+      case state_read_hello_verify_request:
+        ret = do_read_hello_verify_request(hs);
+        break;
+      case state_read_server_hello:
+        ret = do_read_server_hello(hs);
+        break;
+      case state_tls13:
+        ret = do_tls13(hs);
+        break;
+      case state_read_server_certificate:
+        ret = do_read_server_certificate(hs);
+        break;
+      case state_read_certificate_status:
+        ret = do_read_certificate_status(hs);
+        break;
+      case state_verify_server_certificate:
+        ret = do_verify_server_certificate(hs);
+        break;
+      case state_read_server_key_exchange:
+        ret = do_read_server_key_exchange(hs);
+        break;
+      case state_read_certificate_request:
+        ret = do_read_certificate_request(hs);
+        break;
+      case state_read_server_hello_done:
+        ret = do_read_server_hello_done(hs);
+        break;
+      case state_send_client_certificate:
+        ret = do_send_client_certificate(hs);
+        break;
+      case state_send_client_key_exchange:
+        ret = do_send_client_key_exchange(hs);
+        break;
+      case state_send_client_certificate_verify:
+        ret = do_send_client_certificate_verify(hs);
+        break;
+      case state_send_second_client_flight:
+        ret = do_send_second_client_flight(hs);
+        break;
+      case state_send_channel_id:
+        ret = do_send_channel_id(hs);
+        break;
+      case state_send_client_finished:
+        ret = do_send_client_finished(hs);
+        break;
+      case state_finish_flight:
+        ret = do_finish_flight(hs);
+        break;
+      case state_read_session_ticket:
+        ret = do_read_session_ticket(hs);
+        break;
+      case state_process_change_cipher_spec:
+        ret = do_process_change_cipher_spec(hs);
+        break;
+      case state_read_server_finished:
+        ret = do_read_server_finished(hs);
+        break;
+      case state_finish_client_handshake:
+        ret = do_finish_client_handshake(hs);
+        break;
+      case state_done:
+        ret = ssl_hs_ok;
+        break;
+    }
+
+    if (hs->state != state) {
+      ssl_do_info_callback(hs->ssl, SSL_CB_CONNECT_LOOP, 1);
+    }
+
+    if (ret != ssl_hs_ok) {
+      return ret;
+    }
+  }
+
+  ssl_do_info_callback(hs->ssl, SSL_CB_HANDSHAKE_DONE, 1);
+  return ssl_hs_ok;
+}
+
+const char *ssl_client_handshake_state(SSL_HANDSHAKE *hs) {
+  enum ssl_client_hs_state_t state =
+      static_cast<enum ssl_client_hs_state_t>(hs->state);
+  switch (state) {
+    case state_start_connect:
+      return "TLS client start_connect";
+    case state_send_client_hello:
+      return "TLS client send_client_hello";
+    case state_enter_early_data:
+      return "TLS client enter_early_data";
+    case state_read_hello_verify_request:
+      return "TLS client read_hello_verify_request";
+    case state_read_server_hello:
+      return "TLS client read_server_hello";
+    case state_tls13:
+      return tls13_client_handshake_state(hs);
+    case state_read_server_certificate:
+      return "TLS client read_server_certificate";
+    case state_read_certificate_status:
+      return "TLS client read_certificate_status";
+    case state_verify_server_certificate:
+      return "TLS client verify_server_certificate";
+    case state_read_server_key_exchange:
+      return "TLS client read_server_key_exchange";
+    case state_read_certificate_request:
+      return "TLS client read_certificate_request";
+    case state_read_server_hello_done:
+      return "TLS client read_server_hello_done";
+    case state_send_client_certificate:
+      return "TLS client send_client_certificate";
+    case state_send_client_key_exchange:
+      return "TLS client send_client_key_exchange";
+    case state_send_client_certificate_verify:
+      return "TLS client send_client_certificate_verify";
+    case state_send_second_client_flight:
+      return "TLS client send_second_client_flight";
+    case state_send_channel_id:
+      return "TLS client send_channel_id";
+    case state_send_client_finished:
+      return "TLS client send_client_finished";
+    case state_finish_flight:
+      return "TLS client finish_flight";
+    case state_read_session_ticket:
+      return "TLS client read_session_ticket";
+    case state_process_change_cipher_spec:
+      return "TLS client process_change_cipher_spec";
+    case state_read_server_finished:
+      return "TLS client read_server_finished";
+    case state_finish_client_handshake:
+      return "TLS client finish_client_handshake";
+    case state_done:
+      return "TLS client done";
+  }
+
+  return "TLS client unknown";
+}
+
+}