runner: Remove split handshake test machinery from the handshaker

This dramatically simplifies the handshaker binary, as it's now just
responsible for a single request/response operation.

Bug: 376969215
Change-Id: I2748f23d41bfbb8caf4e004d90e1b086f0239193
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/100188
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Lily Chen <chlily@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 2eefbde..36bec75 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -502,9 +502,7 @@
     }
   }
 
-  // early_callback_called is updated in the handshaker, so we don't see it
-  // here.
-  if (!config->handoff && config->is_server && !state->early_callback_called) {
+  if (config->is_server && !state->early_callback_called) {
     fprintf(stderr, "early callback not called\n");
     return false;
   }
@@ -781,8 +779,7 @@
   return true;
 }
 
-static bool DoExchange(bssl::UniquePtr<SSL_SESSION> *out_session,
-                       bssl::UniquePtr<SSL> *ssl_uniqueptr,
+static bool DoExchange(bssl::UniquePtr<SSL_SESSION> *out_session, SSL *ssl,
                        const TestConfig *config, bool is_resume, bool is_retry,
                        SettingsWriter *writer);
 
@@ -869,7 +866,8 @@
     bio.release();  // SSL_set_bio takes ownership.
   }
 
-  bool ret = DoExchange(out_session, &ssl, config, is_resume, false, writer);
+  bool ret =
+      DoExchange(out_session, ssl.get(), config, is_resume, false, writer);
   if (!config->is_server && is_resume && config->expect_reject_early_data) {
     // We must have failed due to an early data rejection.
     if (ret) {
@@ -914,9 +912,9 @@
       return false;
     }
 
-    assert(!config->handoff);
     config = retry_config;
-    ret = DoExchange(out_session, &ssl, retry_config, is_resume, true, writer);
+    ret = DoExchange(out_session, ssl.get(), retry_config, is_resume, true,
+                     writer);
   }
 
   // An ECH rejection appears as a failed connection. Note `ssl` may use a
@@ -978,29 +976,14 @@
   return true;
 }
 
-static bool DoExchange(bssl::UniquePtr<SSL_SESSION> *out_session,
-                       bssl::UniquePtr<SSL> *ssl_uniqueptr,
+static bool DoExchange(bssl::UniquePtr<SSL_SESSION> *out_session, SSL *ssl,
                        const TestConfig *config, bool is_resume, bool is_retry,
                        SettingsWriter *writer) {
   int ret;
-  SSL *ssl = ssl_uniqueptr->get();
   SSL_CTX *session_ctx = SSL_get_SSL_CTX(ssl);
   TestState *test_state = GetTestState(ssl);
 
   if (!config->implicit_handshake) {
-    if (config->handoff) {
-#if defined(HANDSHAKER_SUPPORTED)
-      if (!DoSplitHandshake(ssl_uniqueptr, writer, is_resume)) {
-        return false;
-      }
-      ssl = ssl_uniqueptr->get();
-      test_state = GetTestState(ssl);
-#else
-      fprintf(stderr, "The external handshaker can only be used on Linux\n");
-      return false;
-#endif
-    }
-
     do {
       ret = CheckIdempotentError("SSL_do_handshake", ssl, [&]() -> int {
         return SSL_do_handshake(ssl);
diff --git a/ssl/test/fuzzer.h b/ssl/test/fuzzer.h
index 668442a..c3a37bf 100644
--- a/ssl/test/fuzzer.h
+++ b/ssl/test/fuzzer.h
@@ -335,60 +335,19 @@
       SSL_set_tlsext_host_name(ssl.get(), "hostname");
     }
 
-    // ssl_handoff may or may not be used.
-    bssl::UniquePtr<SSL> ssl_handoff(SSL_new(ctx_.get()));
-    bssl::UniquePtr<SSL> ssl_handback(SSL_new(ctx_.get()));
-    SSL_set_accept_state(ssl_handoff.get());
-
     SSL_set0_rbio(ssl.get(), MakeBIO(CBS_data(&cbs), CBS_len(&cbs)).release());
     SSL_set0_wbio(ssl.get(), BIO_new(BIO_s_mem()));
 
-    SSL *ssl_handshake = ssl.get();
-    bool handshake_successful = false;
-    bool handback_successful = false;
-    for (;;) {
-      int ret = SSL_do_handshake(ssl_handshake);
-      if (ret < 0 && SSL_get_error(ssl_handshake, ret) == SSL_ERROR_HANDOFF) {
-        MoveBIOs(ssl_handoff.get(), ssl.get());
-        // Ordinarily we would call SSL_serialize_handoff(ssl.get().  But for
-        // fuzzing, use the serialized handoff that's getting fuzzed.
-        if (!bssl::SSL_apply_handoff(ssl_handoff.get(), handoff_)) {
-          if (debug_) {
-            fprintf(stderr, "Handoff failed.\n");
-          }
-          break;
-        }
-        ssl_handshake = ssl_handoff.get();
-      } else if (ret < 0 &&
-                 SSL_get_error(ssl_handshake, ret) == SSL_ERROR_HANDBACK) {
-        MoveBIOs(ssl_handback.get(), ssl_handoff.get());
-        if (!bssl::SSL_apply_handback(ssl_handback.get(), handback_)) {
-          if (debug_) {
-            fprintf(stderr, "Handback failed.\n");
-          }
-          break;
-        }
-        handback_successful = true;
-        ssl_handshake = ssl_handback.get();
-      } else {
-        handshake_successful = ret == 1;
-        break;
-      }
-    }
-
-    if (debug_) {
-      if (!handshake_successful) {
-        fprintf(stderr, "Handshake failed.\n");
-      } else if (handback_successful) {
-        fprintf(stderr, "Handback successful.\n");
-      }
+    bool handshake_successful = SSL_do_handshake(ssl.get()) == 1;
+    if (debug_ && !handshake_successful) {
+      fprintf(stderr, "Handshake failed.\n");
     }
 
     if (handshake_successful) {
       // Keep reading application data until error or EOF.
       uint8_t tmp[1024];
       for (;;) {
-        if (SSL_read(ssl_handshake, tmp, sizeof(tmp)) <= 0) {
+        if (SSL_read(ssl.get(), tmp, sizeof(tmp)) <= 0) {
           break;
         }
       }
@@ -506,8 +465,6 @@
     // `ctx` is shared between runs, so we must clear any modifications to it
     // made later on in this function.
     SSL_CTX_flush_sessions(ctx_.get(), 0);
-    handoff_ = {};
-    handback_ = {};
 
     bssl::UniquePtr<SSL> ssl(SSL_new(ctx_.get()));
     if (role_ == kServer) {
@@ -551,28 +508,6 @@
           SSL_set_verify(ssl.get(), SSL_VERIFY_PEER, nullptr);
           break;
 
-        case kHandoffTag: {
-          CBS handoff;
-          if (!CBS_get_u24_length_prefixed(cbs, &handoff)) {
-            return nullptr;
-          }
-          handoff_.assign(CBS_data(&handoff),
-                          CBS_data(&handoff) + CBS_len(&handoff));
-          bssl::SSL_set_handoff_mode(ssl.get(), 1);
-          break;
-        }
-
-        case kHandbackTag: {
-          CBS handback;
-          if (!CBS_get_u24_length_prefixed(cbs, &handback)) {
-            return nullptr;
-          }
-          handback_.assign(CBS_data(&handback),
-                           CBS_data(&handback) + CBS_len(&handback));
-          bssl::SSL_set_handoff_mode(ssl.get(), 1);
-          break;
-        }
-
         case kHintsTag: {
           CBS hints;
           if (!CBS_get_u24_length_prefixed(cbs, &hints)) {
@@ -644,7 +579,6 @@
   Protocol protocol_;
   Role role_;
   bssl::UniquePtr<SSL_CTX> ctx_;
-  std::vector<uint8_t> handoff_, handback_;
 };
 
 }  // namespace
diff --git a/ssl/test/fuzzer_tags.h b/ssl/test/fuzzer_tags.h
index 26f8b1f..acc3489 100644
--- a/ssl/test/fuzzer_tags.h
+++ b/ssl/test/fuzzer_tags.h
@@ -39,11 +39,7 @@
 // certificates.
 static const uint16_t kRequestClientCert = 2;
 
-// kHandoffTag is followed by the output of `SSL_serialize_handoff`.
-static const uint16_t kHandoffTag = 3;
-
-// kHandbackTag is followed by the output of `SSL_serialize_handback`.
-static const uint16_t kHandbackTag = 4;
+// 3 and 4 used to be kHandoffTag and kHandbackTag.
 
 // kHintsTag is followed by the output of `SSL_serialize_handshake_hints`.
 static const uint16_t kHintsTag = 5;
diff --git a/ssl/test/handshake_util.cc b/ssl/test/handshake_util.cc
index 6a5502e..441f15c 100644
--- a/ssl/test/handshake_util.cc
+++ b/ssl/test/handshake_util.cc
@@ -138,24 +138,6 @@
 
 #if defined(HANDSHAKER_SUPPORTED)
 
-// MoveBIOs moves the `BIO`s of `src` to `dst`.  It is used for handoff.
-static void MoveBIOs(SSL *dest, SSL *src) {
-  BIO *rbio = SSL_get_rbio(src);
-  BIO_up_ref(rbio);
-  SSL_set0_rbio(dest, rbio);
-
-  BIO *wbio = SSL_get_wbio(src);
-  BIO_up_ref(wbio);
-  SSL_set0_wbio(dest, wbio);
-
-  SSL_set0_rbio(src, nullptr);
-  SSL_set0_wbio(src, nullptr);
-}
-
-static bool HandoffReady(SSL *ssl, int ret) {
-  return ret < 0 && SSL_get_error(ssl, ret) == SSL_ERROR_HANDOFF;
-}
-
 static ssize_t read_eintr(int fd, void *out, size_t len) {
   ssize_t ret;
   do {
@@ -180,130 +162,6 @@
   return ret;
 }
 
-// Proxy relays data between `socket`, which is connected to the client, and the
-// handshaker, which is connected to the numerically specified file descriptors,
-// until the handshaker returns control.
-static bool Proxy(BIO *socket, bool async, int control, int rfd, int wfd) {
-  for (;;) {
-    fd_set rfds;
-    FD_ZERO(&rfds);
-    FD_SET(wfd, &rfds);
-    FD_SET(control, &rfds);
-    int fd_max = wfd > control ? wfd : control;
-    if (select(fd_max + 1, &rfds, nullptr, nullptr, nullptr) == -1) {
-      perror("select");
-      return false;
-    }
-
-    char buf[64];
-    ssize_t bytes;
-    if (FD_ISSET(wfd, &rfds) &&
-        (bytes = read_eintr(wfd, buf, sizeof(buf))) > 0) {
-      char *b = buf;
-      while (bytes) {
-        int written = BIO_write(socket, b, bytes);
-        if (!written) {
-          fprintf(stderr, "BIO_write wrote nothing\n");
-          return false;
-        }
-        if (written < 0) {
-          if (async) {
-            AsyncBioAllowWrite(socket, 1);
-            continue;
-          }
-          fprintf(stderr, "BIO_write failed\n");
-          return false;
-        }
-        b += written;
-        bytes -= written;
-      }
-      // Flush all pending data from the handshaker to the client before
-      // considering control messages.
-      continue;
-    }
-
-    if (!FD_ISSET(control, &rfds)) {
-      continue;
-    }
-
-    char msg;
-    if (read_eintr(control, &msg, 1) != 1) {
-      perror("read");
-      return false;
-    }
-    switch (msg) {
-      case kControlMsgDone:
-        return true;
-      case kControlMsgError:
-        return false;
-      case kControlMsgUnimplemented:
-        exit(kExitCodeUnimplemented);
-      case kControlMsgWantRead:
-        break;
-      default:
-        fprintf(stderr, "Unknown control message from handshaker: %c\n", msg);
-        return false;
-    }
-
-    auto proxy_data = [&](uint8_t *out, size_t len) -> bool {
-      if (async) {
-        AsyncBioAllowRead(socket, len);
-      }
-
-      while (len > 0) {
-        int bytes_read = BIO_read(socket, out, len);
-        if (bytes_read < 1) {
-          fprintf(stderr, "BIO_read failed\n");
-          return false;
-        }
-
-        ssize_t bytes_written = write_eintr(rfd, out, bytes_read);
-        if (bytes_written == -1) {
-          perror("write");
-          return false;
-        }
-        if (bytes_written != bytes_read) {
-          fprintf(stderr, "short write (%zd of %d bytes)\n", bytes_written,
-                  bytes_read);
-          return false;
-        }
-
-        len -= bytes_read;
-        out += bytes_read;
-      }
-      return true;
-    };
-
-    // Process one SSL record at a time.  That way, we don't send the handshaker
-    // anything it doesn't want to process, e.g. early data.
-    uint8_t header[SSL3_RT_HEADER_LENGTH];
-    if (!proxy_data(header, sizeof(header))) {
-      return false;
-    }
-    if (header[1] != 3) {
-       fprintf(stderr, "bad header\n");
-       return false;
-    }
-    size_t remaining = (header[3] << 8) + header[4];
-    while (remaining > 0) {
-      uint8_t readbuf[64];
-      size_t len = remaining > sizeof(readbuf) ? sizeof(readbuf) : remaining;
-      if (!proxy_data(readbuf, len)) {
-        return false;
-      }
-      remaining -= len;
-    }
-
-    // The handshaker blocks on the control channel, so we have to signal
-    // it that the data have been written.
-    msg = kControlMsgWriteCompleted;
-    if (write_eintr(control, &msg, 1) != 1) {
-      perror("write");
-      return false;
-    }
-  }
-}
-
 class ScopedFD {
  public:
   ScopedFD() : fd_(-1) {}
@@ -486,76 +344,6 @@
   return true;
 }
 
-// RunHandshaker forks and execs the handshaker binary, handing off `input`,
-// and, after proxying some amount of handshake traffic, handing back `out`.
-static bool RunHandshaker(BIO *bio, const TestConfig *config, bool is_resume,
-                          Span<const uint8_t> input,
-                          std::vector<uint8_t> *out) {
-  int rfd[2], wfd[2];
-  // We use pipes, rather than some other mechanism, for their buffers.  During
-  // the handshake, this process acts as a dumb proxy until receiving the
-  // handback signal, which arrives asynchronously.  The race condition means
-  // that this process could incorrectly proxy post-handshake data from the
-  // client to the handshaker.
-  //
-  // To avoid this, this process never proxies data to the handshaker that the
-  // handshaker has not explicitly requested as a result of hitting
-  // `SSL_ERROR_WANT_READ`.  Pipes allow the data to sit in a buffer while the
-  // two processes synchronize over the `control` channel.
-  if (pipe(rfd) != 0) {
-    perror("pipe");
-    return false;
-  }
-  ScopedFD rfd0_closer(rfd[0]), rfd1_closer(rfd[1]);
-
-  if (pipe(wfd) != 0) {
-    perror("pipe");
-    return false;
-  }
-  ScopedFD wfd0_closer(wfd[0]), wfd1_closer(wfd[1]);
-
-  ScopedProcess handshaker;
-  ScopedFD control;
-  if (!StartHandshaker(
-          &handshaker, &control, config, is_resume,
-          {{kFdProxyToHandshaker, rfd[0]}, {kFdHandshakerToProxy, wfd[1]}},
-          {rfd[1], wfd[0]})) {
-    return false;
-  }
-
-  rfd0_closer.Reset();
-  wfd1_closer.Reset();
-
-  if (write_eintr(control.fd(), input.data(), input.size()) == -1) {
-    perror("write");
-    return false;
-  }
-  bool ok = Proxy(bio, config->async, control.fd(), rfd[1], wfd[0]);
-  int wstatus;
-  if (!handshaker.Wait(&wstatus)) {
-    perror("waitpid");
-    return false;
-  }
-  if (ok && wstatus) {
-    fprintf(stderr, "handshaker exited irregularly\n");
-    return false;
-  }
-  if (!ok) {
-    return false;  // This is a "good", i.e. expected, error.
-  }
-
-  constexpr size_t kBufSize = 1024 * 1024;
-  std::vector<uint8_t> buf(kBufSize);
-  ssize_t len = read_eintr(control.fd(), buf.data(), buf.size());
-  if (len == -1) {
-    perror("read");
-    return false;
-  }
-  buf.resize(len);
-  *out = std::move(buf);
-  return true;
-}
-
 static bool RequestHandshakeHint(const TestConfig *config, bool is_resume,
                                  Span<const uint8_t> input, bool *out_has_hints,
                                  std::vector<uint8_t> *out_hints) {
@@ -613,86 +401,6 @@
   return true;
 }
 
-// PrepareHandoff accepts the `ClientHello` from `ssl` and serializes state to
-// be passed to the handshaker.  The serialized state includes both the SSL
-// handoff, as well test-related state.
-static bool PrepareHandoff(SSL *ssl, SettingsWriter *writer,
-                           std::vector<uint8_t> *out_handoff) {
-  SSL_set_handoff_mode(ssl, 1);
-
-  const TestConfig *config = GetTestConfig(ssl);
-  int ret = -1;
-  do {
-    ret = CheckIdempotentError(
-        "SSL_do_handshake", ssl,
-        [&]() -> int { return SSL_do_handshake(ssl); });
-  } while (!HandoffReady(ssl, ret) &&
-           config->async &&
-           RetryAsync(ssl, ret));
-  if (!HandoffReady(ssl, ret)) {
-    fprintf(stderr, "Handshake failed while waiting for handoff.\n");
-    return false;
-  }
-
-  ScopedCBB cbb;
-  SSL_CLIENT_HELLO hello;
-  if (!CBB_init(cbb.get(), 512) ||
-      !SSL_serialize_handoff(ssl, cbb.get(), &hello) ||
-      !writer->WriteHandoff({CBB_data(cbb.get()), CBB_len(cbb.get())}) ||
-      !SerializeContextState(SSL_get_SSL_CTX(ssl), cbb.get()) ||
-      !GetTestState(ssl)->Serialize(cbb.get())) {
-    fprintf(stderr, "Handoff serialisation failed.\n");
-    return false;
-  }
-  out_handoff->assign(CBB_data(cbb.get()),
-                      CBB_data(cbb.get()) + CBB_len(cbb.get()));
-  return true;
-}
-
-// DoSplitHandshake delegates the SSL handshake to a separate process, called
-// the handshaker.  This process proxies I/O between the handshaker and the
-// client, using the `BIO` from `ssl`.  After a successful handshake, `ssl` is
-// replaced with a new `SSL` object, in a way that is intended to be invisible
-// to the caller.
-bool DoSplitHandshake(UniquePtr<SSL> *ssl, SettingsWriter *writer,
-                      bool is_resume) {
-  assert(SSL_get_rbio(ssl->get()) == SSL_get_wbio(ssl->get()));
-  std::vector<uint8_t> handshaker_input;
-  const TestConfig *config = GetTestConfig(ssl->get());
-  // out is the response from the handshaker, which includes a serialized
-  // handback message, but also serialized updates to the `TestState`.
-  std::vector<uint8_t> out;
-  if (!PrepareHandoff(ssl->get(), writer, &handshaker_input) ||
-      !RunHandshaker(SSL_get_rbio(ssl->get()), config, is_resume,
-                     handshaker_input, &out)) {
-    fprintf(stderr, "Handoff failed.\n");
-    return false;
-  }
-
-  SSL_CTX *ctx = SSL_get_SSL_CTX(ssl->get());
-  UniquePtr<SSL> ssl_handback = config->NewSSL(ctx, nullptr, nullptr);
-  if (!ssl_handback) {
-    return false;
-  }
-  CBS output, handback;
-  CBS_init(&output, out.data(), out.size());
-  if (!CBS_get_u24_length_prefixed(&output, &handback) ||
-      !DeserializeContextState(&output, ctx) ||
-      !SetTestState(ssl_handback.get(), TestState::Deserialize(&output, ctx)) ||
-      !GetTestState(ssl_handback.get()) || !writer->WriteHandback(handback) ||
-      !SSL_apply_handback(ssl_handback.get(), handback)) {
-    fprintf(stderr, "Handback failed.\n");
-    return false;
-  }
-  MoveBIOs(ssl_handback.get(), ssl->get());
-  GetTestState(ssl_handback.get())->async_bio =
-      GetTestState(ssl->get())->async_bio;
-  GetTestState(ssl->get())->async_bio = nullptr;
-
-  *ssl = std::move(ssl_handback);
-  return true;
-}
-
 bool GetHandshakeHint(SSL *ssl, SettingsWriter *writer, bool is_resume,
                       const SSL_CLIENT_HELLO *client_hello) {
   ScopedCBB input;
diff --git a/ssl/test/handshake_util.h b/ssl/test/handshake_util.h
index b1c0ea5..ffacff2 100644
--- a/ssl/test/handshake_util.h
+++ b/ssl/test/handshake_util.h
@@ -39,14 +39,6 @@
 inline constexpr int kExitCodeMustFail = 90;
 
 #if defined(HANDSHAKER_SUPPORTED)
-// DoSplitHandshake delegates the SSL handshake to a separate process, called
-// the handshaker.  This process proxies I/O between the handshaker and the
-// client, using the `BIO` from `ssl`.  After a successful handshake, `ssl` is
-// replaced with a new `SSL` object, in a way that is intended to be invisible
-// to the caller.
-bool DoSplitHandshake(bssl::UniquePtr<SSL> *ssl, SettingsWriter *writer,
-                      bool is_resume);
-
 // GetHandshakeHint requests a handshake hint from the handshaker process and
 // configures the result on `ssl`. It returns true on success and false on
 // error.
diff --git a/ssl/test/handshaker.cc b/ssl/test/handshaker.cc
index 0104de6..67d1abd 100644
--- a/ssl/test/handshaker.cc
+++ b/ssl/test/handshaker.cc
@@ -18,6 +18,7 @@
 #include <signal.h>
 #include <unistd.h>
 
+#include <cstdio>
 #include <memory>
 
 #include <openssl/bytestring.h>
@@ -48,94 +49,6 @@
   return ret;
 }
 
-bool HandbackReady(SSL *ssl, int ret) {
-  return ret < 0 && SSL_get_error(ssl, ret) == SSL_ERROR_HANDBACK;
-}
-
-bool Handshaker(const TestConfig *config, int rfd, int wfd,
-                Span<const uint8_t> input, int control) {
-  UniquePtr<SSL_CTX> ctx = config->SetupCtx(/*old_ctx=*/nullptr);
-  if (!ctx) {
-    return false;
-  }
-  UniquePtr<SSL> ssl =
-      config->NewSSL(ctx.get(), /*session=*/nullptr, /*test_state=*/nullptr);
-  if (!ssl) {
-    fprintf(stderr, "Error creating SSL object in handshaker.\n");
-    ERR_print_errors_fp(stderr);
-    return false;
-  }
-
-  // Set `O_NONBLOCK` in order to break out of the loop when we hit
-  // `SSL_ERROR_WANT_READ`, so that we can send `kControlMsgWantRead` to the
-  // proxy.
-  if (fcntl(rfd, F_SETFL, O_NONBLOCK) != 0) {
-    perror("fcntl");
-    return false;
-  }
-  SSL_set_rfd(ssl.get(), rfd);
-  SSL_set_wfd(ssl.get(), wfd);
-
-  CBS cbs, handoff;
-  CBS_init(&cbs, input.data(), input.size());
-  if (!CBS_get_asn1_element(&cbs, &handoff, CBS_ASN1_SEQUENCE) ||
-      !DeserializeContextState(&cbs, ctx.get()) ||
-      !SetTestState(ssl.get(), TestState::Deserialize(&cbs, ctx.get())) ||
-      !GetTestState(ssl.get()) ||
-      !SSL_apply_handoff(ssl.get(), handoff)) {
-    fprintf(stderr, "Handoff application failed.\n");
-    return false;
-  }
-
-  int ret = 0;
-  for (;;) {
-    ret = CheckIdempotentError(
-        "SSL_do_handshake", ssl.get(),
-        [&]() -> int { return SSL_do_handshake(ssl.get()); });
-    if (SSL_get_error(ssl.get(), ret) == SSL_ERROR_WANT_READ) {
-      // Synchronize with the proxy, i.e. don't let the handshake continue until
-      // the proxy has sent more data.
-      char msg = kControlMsgWantRead;
-      if (write_eintr(control, &msg, 1) != 1 ||
-          read_eintr(control, &msg, 1) != 1 ||
-          msg != kControlMsgWriteCompleted) {
-        fprintf(stderr, "read via proxy failed\n");
-        return false;
-      }
-      continue;
-    }
-    if (!RetryAsync(ssl.get(), ret)) {
-      break;
-    }
-  }
-  if (!HandbackReady(ssl.get(), ret)) {
-    fprintf(stderr, "Handshaker: %s\n",
-            SSL_error_description(SSL_get_error(ssl.get(), ret)));
-    ERR_print_errors_fp(stderr);
-    return false;
-  }
-
-  ScopedCBB output;
-  CBB handback;
-  if (!CBB_init(output.get(), 1024) ||
-      !CBB_add_u24_length_prefixed(output.get(), &handback) ||
-      !SSL_serialize_handback(ssl.get(), &handback) ||
-      !SerializeContextState(ctx.get(), output.get()) ||
-      !GetTestState(ssl.get())->Serialize(output.get())) {
-    fprintf(stderr, "Handback serialisation failed.\n");
-    return false;
-  }
-
-  char msg = kControlMsgDone;
-  if (write_eintr(control, &msg, 1) == -1 ||
-      write_eintr(control, CBB_data(output.get()), CBB_len(output.get())) ==
-          -1) {
-    perror("write");
-    return false;
-  }
-  return true;
-}
-
 bool GenerateHandshakeHint(const TestConfig *config,
                            bssl::Span<const uint8_t> request, int control) {
   // The handshake hint contains the ClientHello and the capabilities string.
@@ -267,15 +180,13 @@
   }
 #endif  // FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
 
-  if (config->handshake_hints) {
-    if (!GenerateHandshakeHint(config, request, kFdControl)) {
-      return SignalError();
-    }
-  } else {
-    if (!Handshaker(config, kFdProxyToHandshaker, kFdHandshakerToProxy,
-                    request, kFdControl)) {
-      return SignalError();
-    }
+  if (!config->handshake_hints) {
+    // Historically omitting -handshake-hints ran the split handshakes mode.
+    fprintf(stderr, "Handshaker missing -handshake-hints flag.");
+    return SignalError();
+  }
+  if (!GenerateHandshakeHint(config, request, kFdControl)) {
+    return SignalError();
   }
   return 0;
 }
diff --git a/ssl/test/settings_writer.cc b/ssl/test/settings_writer.cc
index 293702f..234928c 100644
--- a/ssl/test/settings_writer.cc
+++ b/ssl/test/settings_writer.cc
@@ -87,14 +87,6 @@
   return fwrite(settings, settings_len, 1, file.get()) == 1;
 }
 
-bool SettingsWriter::WriteHandoff(bssl::Span<const uint8_t> handoff) {
-  return WriteData(kHandoffTag, handoff);
-}
-
-bool SettingsWriter::WriteHandback(bssl::Span<const uint8_t> handback) {
-  return WriteData(kHandbackTag, handback);
-}
-
 bool SettingsWriter::WriteHints(bssl::Span<const uint8_t> hints) {
   return WriteData(kHintsTag, hints);
 }
diff --git a/ssl/test/settings_writer.h b/ssl/test/settings_writer.h
index 306d095..36a35d3 100644
--- a/ssl/test/settings_writer.h
+++ b/ssl/test/settings_writer.h
@@ -33,8 +33,6 @@
   // Commit writes the buffered data to disk.
   bool Commit();
 
-  bool WriteHandoff(bssl::Span<const uint8_t> handoff);
-  bool WriteHandback(bssl::Span<const uint8_t> handback);
   bool WriteHints(bssl::Span<const uint8_t> hints);
 
  private:
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 9e09491..a86b7b2 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -528,7 +528,6 @@
         StringFlag("-expect-msg-callback", &TestConfig::expect_msg_callback),
         BoolFlag("-allow-false-start-without-alpn",
                  &TestConfig::allow_false_start_without_alpn),
-        BoolFlag("-handoff", &TestConfig::handoff),
         BoolFlag("-handshake-hints", &TestConfig::handshake_hints),
         BoolFlag("-allow-hint-mismatch", &TestConfig::allow_hint_mismatch),
         BoolFlag("-use-ocsp-callback", &TestConfig::use_ocsp_callback),
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 56d347c..e0642c4 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -217,7 +217,6 @@
   bool use_custom_verify_callback = false;
   std::string expect_msg_callback;
   bool allow_false_start_without_alpn = false;
-  bool handoff = false;
   bool handshake_hints = false;
   bool allow_hint_mismatch = false;
   bool use_ocsp_callback = false;
diff --git a/ssl/test/test_state.cc b/ssl/test/test_state.cc
index fc33d58..d439c62 100644
--- a/ssl/test/test_state.cc
+++ b/ssl/test/test_state.cc
@@ -79,103 +79,3 @@
 void CopySessions(SSL_CTX *dst, const SSL_CTX *src) {
   lh_SSL_SESSION_doall_arg(FromOpaque(src)->sessions, ssl_ctx_add_session, dst);
 }
-
-static void push_session(SSL_SESSION *session, void *arg) {
-  auto s = reinterpret_cast<std::vector<SSL_SESSION *> *>(arg);
-  s->push_back(session);
-}
-
-bool SerializeContextState(SSL_CTX *ctx, CBB *cbb) {
-  CBB out, ctx_sessions, ticket_keys;
-  uint8_t keys[48];
-  if (!CBB_add_u24_length_prefixed(cbb, &out) ||
-      !CBB_add_u16(&out, 0 /* version */) ||
-      !SSL_CTX_get_tlsext_ticket_keys(ctx, &keys, sizeof(keys)) ||
-      !CBB_add_u8_length_prefixed(&out, &ticket_keys) ||
-      !CBB_add_bytes(&ticket_keys, keys, sizeof(keys)) ||
-      !CBB_add_asn1(&out, &ctx_sessions, CBS_ASN1_SEQUENCE)) {
-    return false;
-  }
-  std::vector<SSL_SESSION *> sessions;
-  lh_SSL_SESSION_doall_arg(FromOpaque(ctx)->sessions, push_session, &sessions);
-  for (const auto &sess : sessions) {
-    if (!ssl_session_serialize(sess, &ctx_sessions)) {
-      return false;
-    }
-  }
-  return CBB_flush(cbb);
-}
-
-bool DeserializeContextState(CBS *cbs, SSL_CTX *ctx) {
-  CBS in, sessions, ticket_keys;
-  uint16_t version;
-  constexpr uint16_t kVersion = 0;
-  if (!CBS_get_u24_length_prefixed(cbs, &in) ||
-      !CBS_get_u16(&in, &version) ||
-      version > kVersion ||
-      !CBS_get_u8_length_prefixed(&in, &ticket_keys) ||
-      !SSL_CTX_set_tlsext_ticket_keys(ctx, CBS_data(&ticket_keys),
-                                      CBS_len(&ticket_keys)) ||
-      !CBS_get_asn1(&in, &sessions, CBS_ASN1_SEQUENCE)) {
-    return false;
-  }
-  while (CBS_len(&sessions)) {
-    UniquePtr<SSL_SESSION> session = SSL_SESSION_parse(
-        &sessions, FromOpaque(ctx)->x509_method, FromOpaque(ctx)->pool.get());
-    if (!session) {
-      return false;
-    }
-    SSL_CTX_add_session(ctx, session.get());
-  }
-  return true;
-}
-
-bool TestState::Serialize(CBB *cbb) const {
-  CBB out, pending, text;
-  if (!CBB_add_u24_length_prefixed(cbb, &out) ||
-      !CBB_add_u16(&out, 0 /* version */) ||
-      !CBB_add_u24_length_prefixed(&out, &pending) ||
-      (pending_session &&
-       !ssl_session_serialize(pending_session.get(), &pending)) ||
-      !CBB_add_u16_length_prefixed(&out, &text) ||
-      !CBB_add_bytes(
-          &text, reinterpret_cast<const uint8_t *>(msg_callback_text.data()),
-          msg_callback_text.length()) ||
-      !CBB_add_asn1_uint64(&out, g_clock.tv_sec) ||
-      !CBB_add_asn1_uint64(&out, g_clock.tv_usec) ||
-      !CBB_flush(cbb)) {
-    return false;
-  }
-  return true;
-}
-
-std::unique_ptr<TestState> TestState::Deserialize(CBS *cbs, SSL_CTX *ctx) {
-  CBS in, pending_session, text;
-  auto state = std::make_unique<TestState>();
-  uint16_t version;
-  constexpr uint16_t kVersion = 0;
-  uint64_t sec, usec;
-  if (!CBS_get_u24_length_prefixed(cbs, &in) ||  //
-      !CBS_get_u16(&in, &version) ||             //
-      version > kVersion ||
-      !CBS_get_u24_length_prefixed(&in, &pending_session) ||
-      !CBS_get_u16_length_prefixed(&in, &text) ||
-      !CBS_get_asn1_uint64(&in, &sec) ||   //
-      !CBS_get_asn1_uint64(&in, &usec) ||  //
-      usec >= 1000000) {
-    return nullptr;
-  }
-  if (CBS_len(&pending_session)) {
-    state->pending_session =
-        SSL_SESSION_parse(&pending_session, FromOpaque(ctx)->x509_method,
-                          FromOpaque(ctx)->pool.get());
-    if (!state->pending_session) {
-      return nullptr;
-    }
-  }
-  state->msg_callback_text = std::string(
-      reinterpret_cast<const char *>(CBS_data(&text)), CBS_len(&text));
-  g_clock.tv_sec = sec;
-  g_clock.tv_usec = usec;
-  return state;
-}
diff --git a/ssl/test/test_state.h b/ssl/test/test_state.h
index 8856a78..150e47e 100644
--- a/ssl/test/test_state.h
+++ b/ssl/test/test_state.h
@@ -25,17 +25,6 @@
 #include "mock_quic_transport.h"
 
 struct TestState {
-  // Serialize writes `pending_session` and `msg_callback_text` to `out`, for
-  // use in split-handshake tests.  We don't try to serialize every bit of test
-  // state, but serializing `pending_session` is necessary to exercise session
-  // resumption, and `msg_callback_text` is especially useful.  In the general
-  // case, checks of state updated during the handshake can be skipped when
-  // `config->handoff`.
-  bool Serialize(CBB *out) const;
-
-  // Deserialize returns a new `TestState` from data written by `Serialize`.
-  static std::unique_ptr<TestState> Deserialize(CBS *cbs, SSL_CTX *ctx);
-
   // async_bio is async BIO which pauses reads and writes.
   BIO *async_bio = nullptr;
   // packeted_bio is the packeted BIO which simulates read timeouts.
@@ -82,12 +71,4 @@
 
 void CopySessions(SSL_CTX *dest, const SSL_CTX *src);
 
-// SerializeContextState writes session material (sessions and ticket keys) from
-// `ctx` into `cbb`.
-bool SerializeContextState(SSL_CTX *ctx, CBB *cbb);
-
-// DeserializeContextState updates `out` with material previously serialized by
-// SerializeContextState.
-bool DeserializeContextState(CBS *in, SSL_CTX *out);
-
 #endif  // HEADER_TEST_STATE