Lift BIO above SSL_PROTOCOL_METHOD.

This gets us closer to exposing BIO-free APIs. The next step is probably
to make the experimental bssl::OpenRecord function call a split out core
of ssl_read_impl.

Change-Id: I4acebb43f708df8c52eb4e328da8ae3551362fb9
Reviewed-on: https://boringssl-review.googlesource.com/21865
Commit-Queue: Steven Valdez <svaldez@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
Reviewed-by: Steven Valdez <svaldez@google.com>
diff --git a/ssl/d1_both.cc b/ssl/d1_both.cc
index daf62fc..5e5fc4b 100644
--- a/ssl/d1_both.cc
+++ b/ssl/d1_both.cc
@@ -272,9 +272,10 @@
 // queue. Otherwise, it checks |msg_hdr| is consistent with the existing one. It
 // returns NULL on failure. The caller does not take ownership of the result.
 static hm_fragment *dtls1_get_incoming_message(
-    SSL *ssl, const struct hm_header_st *msg_hdr) {
+    SSL *ssl, uint8_t *out_alert, const struct hm_header_st *msg_hdr) {
   if (msg_hdr->seq < ssl->d1->handshake_read_seq ||
       msg_hdr->seq - ssl->d1->handshake_read_seq >= SSL_MAX_HANDSHAKE_FLIGHT) {
+    *out_alert = SSL_AD_INTERNAL_ERROR;
     return NULL;
   }
 
@@ -287,7 +288,7 @@
     if (frag->type != msg_hdr->type ||
         frag->msg_len != msg_hdr->msg_len) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_FRAGMENT_MISMATCH);
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+      *out_alert = SSL_AD_ILLEGAL_PARAMETER;
       return NULL;
     }
     return frag;
@@ -296,81 +297,76 @@
   // This is the first fragment from this message.
   frag = dtls1_hm_fragment_new(msg_hdr);
   if (frag == NULL) {
+    *out_alert = SSL_AD_INTERNAL_ERROR;
     return NULL;
   }
   ssl->d1->incoming_messages[idx] = frag;
   return frag;
 }
 
-int dtls1_read_message(SSL *ssl) {
-  SSL3_RECORD *rr = &ssl->s3->rrec;
-  if (rr->length == 0) {
-    int ret = dtls1_get_record(ssl);
-    if (ret <= 0) {
-      return ret;
-    }
+ssl_open_record_t dtls1_open_handshake(SSL *ssl, size_t *out_consumed,
+                                       uint8_t *out_alert, Span<uint8_t> in) {
+  uint8_t type;
+  Span<uint8_t> record;
+  auto ret = dtls_open_record(ssl, &type, &record, out_consumed, out_alert, in);
+  if (ret != ssl_open_record_success) {
+    return ret;
   }
 
-  switch (rr->type) {
+  switch (type) {
     case SSL3_RT_APPLICATION_DATA:
       // Unencrypted application data records are always illegal.
       if (ssl->s3->aead_read_ctx->is_null_cipher()) {
-        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
         OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
-        return -1;
+        *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
+        return ssl_open_record_error;
       }
 
       // Out-of-order application data may be received between ChangeCipherSpec
       // and finished. Discard it.
-      rr->length = 0;
-      ssl_read_buffer_discard(ssl);
-      return 1;
+      return ssl_open_record_discard;
 
     case SSL3_RT_CHANGE_CIPHER_SPEC:
       // We do not support renegotiation, so encrypted ChangeCipherSpec records
       // are illegal.
       if (!ssl->s3->aead_read_ctx->is_null_cipher()) {
-        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
         OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
-        return -1;
+        *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
+        return ssl_open_record_error;
       }
 
-      if (rr->length != 1 || rr->data[0] != SSL3_MT_CCS) {
+      if (record.size() != 1u || record[0] != SSL3_MT_CCS) {
         OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_CHANGE_CIPHER_SPEC);
-        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-        return -1;
+        *out_alert = SSL_AD_ILLEGAL_PARAMETER;
+        return ssl_open_record_error;
       }
 
       // Flag the ChangeCipherSpec for later.
       ssl->d1->has_change_cipher_spec = true;
       ssl_do_msg_callback(ssl, 0 /* read */, SSL3_RT_CHANGE_CIPHER_SPEC,
-                          MakeSpan(rr->data, rr->length));
-
-      rr->length = 0;
-      ssl_read_buffer_discard(ssl);
-      return 1;
+                          record);
+      return ssl_open_record_success;
 
     case SSL3_RT_HANDSHAKE:
       // Break out to main processing.
       break;
 
     default:
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
       OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
-      return -1;
+      *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
+      return ssl_open_record_error;
   }
 
   CBS cbs;
-  CBS_init(&cbs, rr->data, rr->length);
-
+  CBS_init(&cbs, record.data(), record.size());
   while (CBS_len(&cbs) > 0) {
     // Read a handshake fragment.
     struct hm_header_st msg_hdr;
     CBS body;
     if (!dtls1_parse_fragment(&cbs, &msg_hdr, &body)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_HANDSHAKE_RECORD);
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-      return -1;
+      *out_alert = SSL_AD_DECODE_ERROR;
+      return ssl_open_record_error;
     }
 
     const size_t frag_off = msg_hdr.frag_off;
@@ -380,15 +376,15 @@
         frag_off + frag_len > msg_len ||
         msg_len > ssl_max_handshake_message_len(ssl)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_EXCESSIVE_MESSAGE_SIZE);
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-      return -1;
+      *out_alert = SSL_AD_ILLEGAL_PARAMETER;
+      return ssl_open_record_error;
     }
 
     // The encrypted epoch in DTLS has only one handshake message.
     if (ssl->d1->r_epoch == 1 && msg_hdr.seq != ssl->d1->handshake_read_seq) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
-      return -1;
+      *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
+      return ssl_open_record_error;
     }
 
     if (msg_hdr.seq < ssl->d1->handshake_read_seq ||
@@ -398,9 +394,9 @@
       continue;
     }
 
-    hm_fragment *frag = dtls1_get_incoming_message(ssl, &msg_hdr);
+    hm_fragment *frag = dtls1_get_incoming_message(ssl, out_alert, &msg_hdr);
     if (frag == NULL) {
-      return -1;
+      return ssl_open_record_error;
     }
     assert(frag->msg_len == msg_len);
 
@@ -416,9 +412,7 @@
     dtls1_hm_fragment_mark(frag, frag_off, frag_off + frag_len);
   }
 
-  rr->length = 0;
-  ssl_read_buffer_discard(ssl);
-  return 1;
+  return ssl_open_record_success;
 }
 
 bool dtls1_get_message(SSL *ssl, SSLMessage *out) {
@@ -496,17 +490,21 @@
   return true;
 }
 
-int dtls1_read_change_cipher_spec(SSL *ssl) {
-  // Process handshake records until there is a ChangeCipherSpec.
-  while (!ssl->d1->has_change_cipher_spec) {
-    int ret = dtls1_read_message(ssl);
-    if (ret <= 0) {
+ssl_open_record_t dtls1_open_change_cipher_spec(SSL *ssl, size_t *out_consumed,
+                                                uint8_t *out_alert,
+                                                Span<uint8_t> in) {
+  if (!ssl->d1->has_change_cipher_spec) {
+    // dtls1_open_handshake processes both handshake and ChangeCipherSpec.
+    auto ret = dtls1_open_handshake(ssl, out_consumed, out_alert, in);
+    if (ret != ssl_open_record_success) {
       return ret;
     }
   }
-
-  ssl->d1->has_change_cipher_spec = false;
-  return 1;
+  if (ssl->d1->has_change_cipher_spec) {
+    ssl->d1->has_change_cipher_spec = false;
+    return ssl_open_record_success;
+  }
+  return ssl_open_record_discard;
 }
 
 
diff --git a/ssl/d1_pkt.cc b/ssl/d1_pkt.cc
index 5b1cce5..7e329e3 100644
--- a/ssl/d1_pkt.cc
+++ b/ssl/d1_pkt.cc
@@ -128,84 +128,29 @@
 
 namespace bssl {
 
-int dtls1_get_record(SSL *ssl) {
-  for (;;) {
-    Span<uint8_t> body;
-    uint8_t type, alert;
-    size_t consumed;
-    enum ssl_open_record_t open_ret = dtls_open_record(
-        ssl, &type, &body, &consumed, &alert, ssl_read_buffer(ssl));
-    if (open_ret != ssl_open_record_partial) {
-      ssl_read_buffer_consume(ssl, consumed);
-    }
-    switch (open_ret) {
-      case ssl_open_record_partial: {
-        assert(ssl_read_buffer(ssl).empty());
-        int read_ret = ssl_read_buffer_extend_to(ssl, 0 /* unused */);
-        if (read_ret <= 0) {
-          return read_ret;
-        }
-        continue;
-      }
-
-      case ssl_open_record_success: {
-        if (body.size() > 0xffff) {
-          OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
-          return -1;
-        }
-
-        SSL3_RECORD *rr = &ssl->s3->rrec;
-        rr->type = type;
-        rr->length = static_cast<uint16_t>(body.size());
-        rr->data = body.data();
-        return 1;
-      }
-
-      case ssl_open_record_discard:
-        continue;
-
-      case ssl_open_record_close_notify:
-        return 0;
-
-      case ssl_open_record_error:
-        if (alert != 0) {
-          ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
-        }
-        return -1;
-    }
-
-    assert(0);
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
-  }
-}
-
-int dtls1_read_app_data(SSL *ssl, bool *out_got_handshake, uint8_t *buf,
-                        int len, int peek) {
+ssl_open_record_t dtls1_open_app_data(SSL *ssl, Span<uint8_t> *out,
+                                      size_t *out_consumed, uint8_t *out_alert,
+                                      Span<uint8_t> in) {
   assert(!SSL_in_init(ssl));
 
-  *out_got_handshake = false;
-  SSL3_RECORD *rr = &ssl->s3->rrec;
-
-again:
-  if (rr->length == 0) {
-    int ret = dtls1_get_record(ssl);
-    if (ret <= 0) {
-      return ret;
-    }
+  uint8_t type;
+  Span<uint8_t> record;
+  auto ret = dtls_open_record(ssl, &type, &record, out_consumed, out_alert, in);
+  if (ret != ssl_open_record_success) {
+    return ret;
   }
 
-  if (rr->type == SSL3_RT_HANDSHAKE) {
+  if (type == SSL3_RT_HANDSHAKE) {
     // Parse the first fragment header to determine if this is a pre-CCS or
     // post-CCS handshake record. DTLS resets handshake message numbers on each
     // handshake, so renegotiations and retransmissions are ambiguous.
     CBS cbs, body;
     struct hm_header_st msg_hdr;
-    CBS_init(&cbs, rr->data, rr->length);
+    CBS_init(&cbs, record.data(), record.size());
     if (!dtls1_parse_fragment(&cbs, &msg_hdr, &body)) {
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
       OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_HANDSHAKE_RECORD);
-      return -1;
+      *out_alert = SSL_AD_DECODE_ERROR;
+      return ssl_open_record_error;
     }
 
     if (msg_hdr.type == SSL3_MT_FINISHED &&
@@ -215,62 +160,52 @@
         // Finished, they may not have received ours. Only do this for the
         // first fragment, in case the Finished was fragmented.
         if (!dtls1_check_timeout_num(ssl)) {
-          return -1;
+          *out_alert = 0;  // TODO(davidben): Send an alert?
+          return ssl_open_record_error;
         }
 
         dtls1_retransmit_outgoing_messages(ssl);
       }
-
-      rr->length = 0;
-      goto again;
+      return ssl_open_record_discard;
     }
 
     // Otherwise, this is a pre-CCS handshake message from an unsupported
     // renegotiation attempt. Fall through to the error path.
   }
 
-  if (rr->type != SSL3_RT_APPLICATION_DATA) {
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
+  if (type != SSL3_RT_APPLICATION_DATA) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
-    return -1;
+    *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
+    return ssl_open_record_error;
   }
 
-  // Discard empty records.
-  if (rr->length == 0) {
-    goto again;
+  if (record.empty()) {
+    return ssl_open_record_discard;
   }
 
-  if (len <= 0) {
-    return len;
-  }
-
-  if ((unsigned)len > rr->length) {
-    len = rr->length;
-  }
-
-  OPENSSL_memcpy(buf, rr->data, len);
-  if (!peek) {
-    // TODO(davidben): Should the record be truncated instead? This is a
-    // datagram transport. See https://crbug.com/boringssl/65.
-    rr->length -= len;
-    rr->data += len;
-    if (rr->length == 0) {
-      // The record has been consumed, so we may now clear the buffer.
-      ssl_read_buffer_discard(ssl);
-    }
-  }
-
-  return len;
+  *out = record;
+  return ssl_open_record_success;
 }
 
-void dtls1_read_close_notify(SSL *ssl) {
-  // Bidirectional shutdown doesn't make sense for an unordered transport. DTLS
-  // alerts also aren't delivered reliably, so we may even time out because the
-  // peer never received our close_notify. Report to the caller that the channel
-  // has fully shut down.
-  if (ssl->s3->read_shutdown == ssl_shutdown_none) {
-    ssl->s3->read_shutdown = ssl_shutdown_close_notify;
+ssl_open_record_t dtls1_open_close_notify(SSL *ssl, size_t *out_consumed,
+                                          uint8_t *out_alert,
+                                          bssl::Span<uint8_t> in) {
+  switch (ssl->s3->read_shutdown) {
+    // Bidirectional shutdown doesn't make sense for an unordered transport.
+    // DTLS alerts also aren't delivered reliably, so we may even time out
+    // because the peer never received our close_notify. Report to the caller
+    // that the channel has fully shut down.
+    case ssl_shutdown_none:
+    case ssl_shutdown_close_notify:
+      ssl->s3->read_shutdown = ssl_shutdown_close_notify;
+      return ssl_open_record_close_notify;
+    case ssl_shutdown_error:
+      ERR_restore_state(ssl->s3->read_error);
+      *out_alert = 0;
+      return ssl_open_record_error;
   }
+  assert(0);
+  return ssl_open_record_error;
 }
 
 int dtls1_write_app_data(SSL *ssl, bool *out_needs_handshake,
diff --git a/ssl/dtls_method.cc b/ssl/dtls_method.cc
index a6156a1..ac06842 100644
--- a/ssl/dtls_method.cc
+++ b/ssl/dtls_method.cc
@@ -117,11 +117,11 @@
     dtls1_new,
     dtls1_free,
     dtls1_get_message,
-    dtls1_read_message,
     dtls1_next_message,
-    dtls1_read_app_data,
-    dtls1_read_change_cipher_spec,
-    dtls1_read_close_notify,
+    dtls1_open_handshake,
+    dtls1_open_change_cipher_spec,
+    dtls1_open_app_data,
+    dtls1_open_close_notify,
     dtls1_write_app_data,
     dtls1_dispatch_alert,
     dtls1_supports_cipher,
diff --git a/ssl/handshake.cc b/ssl/handshake.cc
index 1e19e5c..a318ec3 100644
--- a/ssl/handshake.cc
+++ b/ssl/handshake.cc
@@ -498,12 +498,22 @@
       }
 
       case ssl_hs_read_server_hello:
-      case ssl_hs_read_message: {
-        int ret = ssl->method->read_message(ssl);
-        if (ret <= 0) {
+      case ssl_hs_read_message:
+      case ssl_hs_read_change_cipher_spec: {
+        uint8_t alert = SSL_AD_DECODE_ERROR;
+        size_t consumed = 0;
+        ssl_open_record_t ret;
+        if (hs->wait == ssl_hs_read_change_cipher_spec) {
+          ret = ssl->method->open_change_cipher_spec(ssl, &consumed, &alert,
+                                                     ssl_read_buffer(ssl));
+        } else {
+          ret = ssl->method->open_handshake(ssl, &consumed, &alert,
+                                            ssl_read_buffer(ssl));
+        }
+        if (ret == ssl_open_record_error &&
+            hs->wait == ssl_hs_read_server_hello) {
           uint32_t err = ERR_peek_error();
-          if (hs->wait == ssl_hs_read_server_hello &&
-              ERR_GET_LIB(err) == ERR_LIB_SSL &&
+          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
@@ -514,16 +524,16 @@
             // See https://crbug.com/446505.
             OPENSSL_PUT_ERROR(SSL, SSL_R_HANDSHAKE_FAILURE_ON_CLIENT_HELLO);
           }
-          return ret;
         }
-        break;
-      }
-
-      case ssl_hs_read_change_cipher_spec: {
-        int ret = ssl->method->read_change_cipher_spec(ssl);
-        if (ret <= 0) {
-          return ret;
+        bool retry;
+        int bio_ret = ssl_handle_open_record(ssl, &retry, ret, consumed, alert);
+        if (bio_ret <= 0) {
+          return bio_ret;
         }
+        if (retry) {
+          continue;
+        }
+        ssl_read_buffer_discard(ssl);
         break;
       }
 
diff --git a/ssl/internal.h b/ssl/internal.h
index 60e69f9..6bbde76 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1062,6 +1062,12 @@
 // zero-initializes it.
 void ssl_read_buffer_clear(SSL *ssl);
 
+// ssl_handle_open_record handles the result of passing |ssl_read_buffer| to a
+// record-processing function. If |ret| is a success or if the caller should
+// retry, it returns one and sets |*out_retry|. Otherwise, it returns <= 0.
+int ssl_handle_open_record(SSL *ssl, bool *out_retry, ssl_open_record_t ret,
+                           size_t consumed, uint8_t alert);
+
 // ssl_write_buffer_is_pending returns one if the write buffer has pending data
 // and zero if is empty.
 int ssl_write_buffer_is_pending(const SSL *ssl);
@@ -1728,20 +1734,31 @@
   // get_message sets |*out| to the current handshake message and returns true
   // if one has been received. It returns false if more input is needed.
   bool (*get_message)(SSL *ssl, SSLMessage *out);
-  // read_message reads additional handshake data for |get_message|. On success,
-  // it returns one. Otherwise, it returns <= 0.
-  int (*read_message)(SSL *ssl);
   // next_message is called to release the current handshake message.
   void (*next_message)(SSL *ssl);
-  // read_app_data reads up to |len| bytes of application data into |buf|. On
-  // success, it returns the number of bytes read. Otherwise, it returns <= 0
-  // and sets |*out_got_handshake| to whether the failure was due to a
-  // post-handshake handshake message. If so, any handshake messages consumed
-  // may be read with |get_message|.
-  int (*read_app_data)(SSL *ssl, bool *out_got_handshake, uint8_t *buf, int len,
-                       int peek);
-  int (*read_change_cipher_spec)(SSL *ssl);
-  void (*read_close_notify)(SSL *ssl);
+  // open_handshake processes a record from |in| for reading a handshake
+  // message.
+  ssl_open_record_t (*open_handshake)(SSL *ssl, size_t *out_consumed,
+                                      uint8_t *out_alert, Span<uint8_t> in);
+  // open_change_cipher_spec processes a record from |in| for reading a
+  // ChangeCipherSpec. If an out-of-order record was received in DTLS, it
+  // succeeds without consuming input.
+  ssl_open_record_t (*open_change_cipher_spec)(SSL *ssl, size_t *out_consumed,
+                                               uint8_t *out_alert,
+                                               Span<uint8_t> in);
+  // open_app_data processes a record from |in| for reading application data.
+  // On success, it returns |ssl_open_record_success| and sets |*out| to the
+  // input. If it encounters a post-handshake message, it returns
+  // |ssl_open_record_discard|. The caller should then retry, after processing
+  // any messages received with |get_message|.
+  ssl_open_record_t (*open_app_data)(SSL *ssl, Span<uint8_t> *out,
+                                     size_t *out_consumed, uint8_t *out_alert,
+                                     Span<uint8_t> in);
+  // open_close_notify processes a record from |in| for reading close_notify.
+  // It discards all records and returns |ssl_open_record_close_notify| when it
+  // receives one.
+  ssl_open_record_t (*open_close_notify)(SSL *ssl, size_t *out_consumed,
+                                         uint8_t *out_alert, Span<uint8_t> in);
   int (*write_app_data)(SSL *ssl, bool *out_needs_handshake, const uint8_t *buf,
                         int len);
   int (*dispatch_alert)(SSL *ssl);
@@ -2107,15 +2124,6 @@
   bool ed25519_enabled:1;
 };
 
-struct SSL3_RECORD {
-  // type is the record type.
-  uint8_t type;
-  // length is the number of unconsumed bytes in the record.
-  uint16_t length;
-  // data is a non-owning pointer to the first unconsumed byte of the record.
-  uint8_t *data;
-};
-
 struct SSL3_BUFFER {
   // buf is the memory allocated for this buffer.
   uint8_t *buf;
@@ -2147,7 +2155,9 @@
   // write_buffer holds data to be written to the transport.
   SSL3_BUFFER write_buffer;
 
-  SSL3_RECORD rrec;  // each decoded record goes in here
+  // pending_app_data is the unconsumed application data. It points into
+  // |read_buffer|.
+  Span<uint8_t> pending_app_data;
 
   // partial write - check the numbers match
   unsigned int wnum;  // number of bytes sent so far
@@ -2690,18 +2700,19 @@
 
 int ssl_send_alert(SSL *ssl, int level, int desc);
 bool ssl3_get_message(SSL *ssl, SSLMessage *out);
-int ssl3_read_message(SSL *ssl);
+ssl_open_record_t ssl3_open_handshake(SSL *ssl, size_t *out_consumed,
+                                      uint8_t *out_alert, Span<uint8_t> in);
 void ssl3_next_message(SSL *ssl);
 
 int ssl3_dispatch_alert(SSL *ssl);
-int ssl3_read_app_data(SSL *ssl, bool *out_got_handshake, uint8_t *buf, int len,
-                       int peek);
-int ssl3_read_change_cipher_spec(SSL *ssl);
-void ssl3_read_close_notify(SSL *ssl);
-// ssl3_get_record reads a new input record. On success, it places it in
-// |ssl->s3->rrec| and returns one. Otherwise it returns <= 0 on error or if
-// more data is needed.
-int ssl3_get_record(SSL *ssl);
+ssl_open_record_t ssl3_open_app_data(SSL *ssl, Span<uint8_t> *out,
+                                     size_t *out_consumed, uint8_t *out_alert,
+                                     Span<uint8_t> in);
+ssl_open_record_t ssl3_open_change_cipher_spec(SSL *ssl, size_t *out_consumed,
+                                               uint8_t *out_alert,
+                                               Span<uint8_t> in);
+ssl_open_record_t ssl3_open_close_notify(SSL *ssl, size_t *out_consumed,
+                                         uint8_t *out_alert, Span<uint8_t> in);
 int ssl3_write_app_data(SSL *ssl, bool *out_needs_handshake, const uint8_t *buf,
                         int len);
 
@@ -2730,15 +2741,14 @@
 // on success and false on allocation failure.
 bool ssl_hash_message(SSL_HANDSHAKE *hs, const SSLMessage &msg);
 
-// dtls1_get_record reads a new input record. On success, it places it in
-// |ssl->s3->rrec| and returns one. Otherwise it returns <= 0 on error or if
-// more data is needed.
-int dtls1_get_record(SSL *ssl);
-
-int dtls1_read_app_data(SSL *ssl, bool *out_got_handshake, uint8_t *buf,
-                        int len, int peek);
-int dtls1_read_change_cipher_spec(SSL *ssl);
-void dtls1_read_close_notify(SSL *ssl);
+ssl_open_record_t dtls1_open_app_data(SSL *ssl, Span<uint8_t> *out,
+                                      size_t *out_consumed, uint8_t *out_alert,
+                                      Span<uint8_t> in);
+ssl_open_record_t dtls1_open_change_cipher_spec(SSL *ssl, size_t *out_consumed,
+                                                uint8_t *out_alert,
+                                                Span<uint8_t> in);
+ssl_open_record_t dtls1_open_close_notify(SSL *ssl, size_t *out_consumed,
+                                          uint8_t *out_alert, Span<uint8_t> in);
 
 int dtls1_write_app_data(SSL *ssl, bool *out_needs_handshake,
                          const uint8_t *buf, int len);
@@ -2762,7 +2772,8 @@
 void dtls1_free(SSL *ssl);
 
 bool dtls1_get_message(SSL *ssl, SSLMessage *out);
-int dtls1_read_message(SSL *ssl);
+ssl_open_record_t dtls1_open_handshake(SSL *ssl, size_t *out_consumed,
+                                       uint8_t *out_alert, Span<uint8_t> in);
 void dtls1_next_message(SSL *ssl);
 int dtls1_dispatch_alert(SSL *ssl);
 
diff --git a/ssl/s3_both.cc b/ssl/s3_both.cc
index 7fc843e..b513a42 100644
--- a/ssl/s3_both.cc
+++ b/ssl/s3_both.cc
@@ -274,55 +274,28 @@
   return 1;
 }
 
-static int read_v2_client_hello(SSL *ssl) {
-  // Read the first 5 bytes, the size of the TLS record header. This is
-  // sufficient to detect a V2ClientHello and ensures that we never read beyond
-  // the first record.
-  int ret = ssl_read_buffer_extend_to(ssl, SSL3_RT_HEADER_LENGTH);
-  if (ret <= 0) {
-    return ret;
-  }
-  const uint8_t *p = ssl_read_buffer(ssl).data();
-
-  // Some dedicated error codes for protocol mixups should the application wish
-  // to interpret them differently. (These do not overlap with ClientHello or
-  // V2ClientHello.)
-  if (strncmp("GET ", (const char *)p, 4) == 0 ||
-      strncmp("POST ", (const char *)p, 5) == 0 ||
-      strncmp("HEAD ", (const char *)p, 5) == 0 ||
-      strncmp("PUT ", (const char *)p, 4) == 0) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_HTTP_REQUEST);
-    return -1;
-  }
-  if (strncmp("CONNE", (const char *)p, 5) == 0) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_HTTPS_PROXY_REQUEST);
-    return -1;
-  }
-
-  if ((p[0] & 0x80) == 0 || p[2] != SSL2_MT_CLIENT_HELLO ||
-      p[3] != SSL3_VERSION_MAJOR) {
-    // Not a V2ClientHello.
-    return 1;
-  }
-
+static ssl_open_record_t read_v2_client_hello(SSL *ssl, size_t *out_consumed,
+                                              Span<const uint8_t> in) {
+  *out_consumed = 0;
+  assert(in.size() >= SSL3_RT_HEADER_LENGTH);
   // Determine the length of the V2ClientHello.
-  size_t msg_length = ((p[0] & 0x7f) << 8) | p[1];
+  size_t msg_length = ((in[0] & 0x7f) << 8) | in[1];
   if (msg_length > (1024 * 4)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_RECORD_TOO_LARGE);
-    return -1;
+    return ssl_open_record_error;
   }
   if (msg_length < SSL3_RT_HEADER_LENGTH - 2) {
     // Reject lengths that are too short early. We have already read
     // |SSL3_RT_HEADER_LENGTH| bytes, so we should not attempt to process an
     // (invalid) V2ClientHello which would be shorter than that.
     OPENSSL_PUT_ERROR(SSL, SSL_R_RECORD_LENGTH_MISMATCH);
-    return -1;
+    return ssl_open_record_error;
   }
 
-  // Read the remainder of the V2ClientHello.
-  ret = ssl_read_buffer_extend_to(ssl, 2 + msg_length);
-  if (ret <= 0) {
-    return ret;
+  // Ask for the remainder of the V2ClientHello.
+  if (in.size() < 2 + msg_length) {
+    *out_consumed = 2 + msg_length;
+    return ssl_open_record_partial;
   }
 
   CBS v2_client_hello = CBS(ssl_read_buffer(ssl).subspan(2, msg_length));
@@ -330,7 +303,7 @@
   // hash. This is only ever called at the start of the handshake, so hs is
   // guaranteed to be non-NULL.
   if (!ssl->s3->hs->transcript.Update(v2_client_hello)) {
-    return -1;
+    return ssl_open_record_error;
   }
 
   ssl_do_msg_callback(ssl, 0 /* read */, 0 /* V2ClientHello */,
@@ -349,7 +322,7 @@
       !CBS_get_bytes(&v2_client_hello, &challenge, challenge_length) ||
       CBS_len(&v2_client_hello) != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    return -1;
+    return ssl_open_record_error;
   }
 
   // msg_type has already been checked.
@@ -385,7 +358,7 @@
       !CBB_add_u8(&hello_body, 0) ||
       !CBB_add_u16_length_prefixed(&hello_body, &cipher_suites)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return -1;
+    return ssl_open_record_error;
   }
 
   // Copy the cipher suites.
@@ -393,7 +366,7 @@
     uint32_t cipher_spec;
     if (!CBS_get_u24(&cipher_specs, &cipher_spec)) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-      return -1;
+      return ssl_open_record_error;
     }
 
     // Skip SSLv2 ciphers.
@@ -402,7 +375,7 @@
     }
     if (!CBB_add_u16(&cipher_suites, cipher_spec)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return -1;
+      return ssl_open_record_error;
     }
   }
 
@@ -411,15 +384,12 @@
       !CBB_add_u8(&hello_body, 0) ||
       !CBB_finish(client_hello.get(), NULL, &ssl->init_buf->length)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
+    return ssl_open_record_error;
   }
 
-  // Consume and discard the V2ClientHello.
-  ssl_read_buffer_consume(ssl, 2 + msg_length);
-  ssl_read_buffer_discard(ssl);
-
+  *out_consumed = 2 + msg_length;
   ssl->s3->is_v2_hello = true;
-  return 1;
+  return ssl_open_record_success;
 }
 
 static bool parse_message(const SSL *ssl, SSLMessage *out,
@@ -497,58 +467,92 @@
   return ssl->init_buf != NULL && ssl->init_buf->length > msg_len;
 }
 
-int ssl3_read_message(SSL *ssl) {
+ssl_open_record_t ssl3_open_handshake(SSL *ssl, size_t *out_consumed,
+                                      uint8_t *out_alert, Span<uint8_t> in) {
+  *out_consumed = 0;
   // Re-create the handshake buffer if needed.
   if (ssl->init_buf == NULL) {
     ssl->init_buf = BUF_MEM_new();
     if (ssl->init_buf == NULL) {
-      return -1;
+      *out_alert = SSL_AD_INTERNAL_ERROR;
+      return ssl_open_record_error;
     }
   }
 
   // Bypass the record layer for the first message to handle V2ClientHello.
   if (ssl->server && !ssl->s3->v2_hello_done) {
-    int ret = read_v2_client_hello(ssl);
-    if (ret > 0) {
-      ssl->s3->v2_hello_done = true;
+    // Ask for the first 5 bytes, the size of the TLS record header. This is
+    // sufficient to detect a V2ClientHello and ensures that we never read
+    // beyond the first record.
+    if (in.size() < SSL3_RT_HEADER_LENGTH) {
+      *out_consumed = SSL3_RT_HEADER_LENGTH;
+      return ssl_open_record_partial;
     }
-    return ret;
-  }
 
-  SSL3_RECORD *rr = &ssl->s3->rrec;
-  // Get new packet if necessary.
-  if (rr->length == 0) {
-    int ret = ssl3_get_record(ssl);
-    if (ret <= 0) {
+    // Some dedicated error codes for protocol mixups should the application
+    // wish to interpret them differently. (These do not overlap with
+    // ClientHello or V2ClientHello.)
+    const char *str = reinterpret_cast<const char*>(in.data());
+    if (strncmp("GET ", str, 4) == 0 ||
+        strncmp("POST ", str, 5) == 0 ||
+        strncmp("HEAD ", str, 5) == 0 ||
+        strncmp("PUT ", str, 4) == 0) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_HTTP_REQUEST);
+      *out_alert = 0;
+      return ssl_open_record_error;
+    }
+    if (strncmp("CONNE", str, 5) == 0) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_HTTPS_PROXY_REQUEST);
+      *out_alert = 0;
+      return ssl_open_record_error;
+    }
+
+    // Check for a V2ClientHello.
+    if ((in[0] & 0x80) != 0 && in[2] == SSL2_MT_CLIENT_HELLO &&
+        in[3] == SSL3_VERSION_MAJOR) {
+      auto ret = read_v2_client_hello(ssl, out_consumed, in);
+      if (ret == ssl_open_record_error) {
+        *out_alert = 0;
+      } else if (ret == ssl_open_record_success) {
+        ssl->s3->v2_hello_done = true;
+      }
       return ret;
     }
+
+    ssl->s3->v2_hello_done = true;
+  }
+
+  uint8_t type;
+  Span<uint8_t> body;
+  auto ret = tls_open_record(ssl, &type, &body, out_consumed, out_alert, in);
+  if (ret != ssl_open_record_success) {
+    return ret;
   }
 
   // WatchGuard's TLS 1.3 interference bug is very distinctive: they drop the
   // ServerHello and send the remaining encrypted application data records
   // as-is. This manifests as an application data record when we expect
   // handshake. Report a dedicated error code for this case.
-  if (!ssl->server && rr->type == SSL3_RT_APPLICATION_DATA &&
+  if (!ssl->server && type == SSL3_RT_APPLICATION_DATA &&
       ssl->s3->aead_read_ctx->is_null_cipher()) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_APPLICATION_DATA_INSTEAD_OF_HANDSHAKE);
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
-    return -1;
+    *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
+    return ssl_open_record_error;
   }
 
-  if (rr->type != SSL3_RT_HANDSHAKE) {
+  if (type != SSL3_RT_HANDSHAKE) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
-    return -1;
+    *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
+    return ssl_open_record_error;
   }
 
   // Append the entire handshake record to the buffer.
-  if (!BUF_MEM_append(ssl->init_buf, rr->data, rr->length)) {
-    return -1;
+  if (!BUF_MEM_append(ssl->init_buf, body.data(), body.size())) {
+    *out_alert = SSL_AD_INTERNAL_ERROR;
+    return ssl_open_record_error;
   }
 
-  rr->length = 0;
-  ssl_read_buffer_discard(ssl);
-  return 1;
+  return ssl_open_record_success;
 }
 
 void ssl3_next_message(SSL *ssl) {
diff --git a/ssl/s3_pkt.cc b/ssl/s3_pkt.cc
index 2718fde..509f8d3 100644
--- a/ssl/s3_pkt.cc
+++ b/ssl/s3_pkt.cc
@@ -126,57 +126,6 @@
 
 static int do_ssl3_write(SSL *ssl, int type, const uint8_t *buf, unsigned len);
 
-int ssl3_get_record(SSL *ssl) {
-  for (;;) {
-    Span<uint8_t> body;
-    uint8_t type, alert = SSL_AD_DECODE_ERROR;
-    size_t consumed;
-    enum ssl_open_record_t open_ret = tls_open_record(
-        ssl, &type, &body, &consumed, &alert, ssl_read_buffer(ssl));
-    if (open_ret != ssl_open_record_partial) {
-      ssl_read_buffer_consume(ssl, consumed);
-    }
-    switch (open_ret) {
-      case ssl_open_record_partial: {
-        int read_ret = ssl_read_buffer_extend_to(ssl, consumed);
-        if (read_ret <= 0) {
-          return read_ret;
-        }
-        continue;
-      }
-
-      case ssl_open_record_success: {
-        if (body.size() > 0xffff) {
-          OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
-          return -1;
-        }
-
-        SSL3_RECORD *rr = &ssl->s3->rrec;
-        rr->type = type;
-        rr->length = static_cast<uint16_t>(body.size());
-        rr->data = body.data();
-        return 1;
-      }
-
-      case ssl_open_record_discard:
-        continue;
-
-      case ssl_open_record_close_notify:
-        return 0;
-
-      case ssl_open_record_error:
-        if (alert != 0) {
-          ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
-        }
-        return -1;
-    }
-
-    assert(0);
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
-  }
-}
-
 int ssl3_write_app_data(SSL *ssl, bool *out_needs_handshake, const uint8_t *buf,
                         int len) {
   assert(ssl_can_write(ssl));
@@ -333,152 +282,120 @@
   return ssl3_write_pending(ssl, type, buf, len);
 }
 
-static int consume_record(SSL *ssl, uint8_t *out, int len, int peek) {
-  SSL3_RECORD *rr = &ssl->s3->rrec;
-
-  if (len <= 0) {
-    return len;
-  }
-
-  if (len > (int)rr->length) {
-    len = (int)rr->length;
-  }
-
-  OPENSSL_memcpy(out, rr->data, len);
-  if (!peek) {
-    rr->length -= len;
-    rr->data += len;
-    if (rr->length == 0) {
-      // The record has been consumed, so we may now clear the buffer.
-      ssl_read_buffer_discard(ssl);
-    }
-  }
-  return len;
-}
-
-int ssl3_read_app_data(SSL *ssl, bool *out_got_handshake, uint8_t *buf, int len,
-                       int peek) {
+ssl_open_record_t ssl3_open_app_data(SSL *ssl, Span<uint8_t> *out,
+                                     size_t *out_consumed, uint8_t *out_alert,
+                                     Span<uint8_t> in) {
   assert(ssl_can_read(ssl));
   assert(!ssl->s3->aead_read_ctx->is_null_cipher());
-  *out_got_handshake = false;
 
-  SSL3_RECORD *rr = &ssl->s3->rrec;
+  uint8_t type;
+  Span<uint8_t> body;
+  auto ret = tls_open_record(ssl, &type, &body, out_consumed, out_alert, in);
+  if (ret != ssl_open_record_success) {
+    return ret;
+  }
 
-  for (;;) {
-    // Get new packet if necessary.
-    if (rr->length == 0) {
-      int ret = ssl3_get_record(ssl);
-      if (ret <= 0) {
-        return ret;
-      }
-    }
+  const bool is_early_data_read = ssl->server && SSL_in_early_data(ssl);
 
-    const bool is_early_data_read = ssl->server && SSL_in_early_data(ssl);
-
-    if (rr->type == SSL3_RT_HANDSHAKE) {
-      // If reading 0-RTT data, reject handshake data. 0-RTT data is terminated
-      // by an alert.
-      if (is_early_data_read) {
-        OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
-        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
-        return -1;
-      }
-
-      // Post-handshake data prior to TLS 1.3 is always renegotiation, which we
-      // never accept as a server. Otherwise |ssl3_get_message| will send
-      // |SSL_R_EXCESSIVE_MESSAGE_SIZE|.
-      if (ssl->server && ssl_protocol_version(ssl) < TLS1_3_VERSION) {
-        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_NO_RENEGOTIATION);
-        OPENSSL_PUT_ERROR(SSL, SSL_R_NO_RENEGOTIATION);
-        return -1;
-      }
-
-      if (ssl->init_buf == NULL) {
-        ssl->init_buf = BUF_MEM_new();
-      }
-      if (ssl->init_buf == NULL ||
-          !BUF_MEM_append(ssl->init_buf, rr->data, rr->length)) {
-        return -1;
-      }
-      *out_got_handshake = true;
-      rr->length = 0;
-      ssl_read_buffer_discard(ssl);
-      return -1;
-    }
-
-    // Handle the end_of_early_data alert.
-    if (rr->type == SSL3_RT_ALERT &&
-        rr->length == 2 &&
-        rr->data[0] == SSL3_AL_WARNING &&
-        rr->data[1] == TLS1_AD_END_OF_EARLY_DATA &&
-        is_early_data_read) {
-      // Consume the record.
-      rr->length = 0;
-      ssl_read_buffer_discard(ssl);
-      // Stop accepting early data.
-      ssl->s3->hs->can_early_read = false;
-      *out_got_handshake = true;
-      return -1;
-    }
-
-    if (rr->type != SSL3_RT_APPLICATION_DATA) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
-      return -1;
-    }
-
+  if (type == SSL3_RT_HANDSHAKE) {
+    // If reading 0-RTT data, reject handshake data. 0-RTT data is terminated
+    // by an alert.
     if (is_early_data_read) {
-      if (rr->length > kMaxEarlyDataAccepted - ssl->s3->hs->early_data_read) {
-        OPENSSL_PUT_ERROR(SSL, SSL_R_TOO_MUCH_READ_EARLY_DATA);
-        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL3_AD_UNEXPECTED_MESSAGE);
-        return -1;
-      }
-
-      ssl->s3->hs->early_data_read += rr->length;
+      OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
+      *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
+      return ssl_open_record_error;
     }
 
-    if (rr->length != 0) {
-      return consume_record(ssl, buf, len, peek);
+    // Post-handshake data prior to TLS 1.3 is always renegotiation, which we
+    // never accept as a server. Otherwise |ssl3_get_message| will send
+    // |SSL_R_EXCESSIVE_MESSAGE_SIZE|.
+    if (ssl->server && ssl_protocol_version(ssl) < TLS1_3_VERSION) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_NO_RENEGOTIATION);
+      *out_alert = SSL_AD_NO_RENEGOTIATION;
+      return ssl_open_record_error;
     }
 
-    // Discard empty records and loop again.
-  }
-}
-
-int ssl3_read_change_cipher_spec(SSL *ssl) {
-  SSL3_RECORD *rr = &ssl->s3->rrec;
-  if (rr->length == 0) {
-    int ret = ssl3_get_record(ssl);
-    if (ret <= 0) {
-      return ret;
+    if (ssl->init_buf == NULL) {
+      ssl->init_buf = BUF_MEM_new();
     }
+    if (ssl->init_buf == NULL ||
+        !BUF_MEM_append(ssl->init_buf, body.data(), body.size())) {
+      *out_alert = SSL_AD_INTERNAL_ERROR;
+      return ssl_open_record_error;
+    }
+    return ssl_open_record_discard;
   }
 
-  if (rr->type != SSL3_RT_CHANGE_CIPHER_SPEC) {
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
+  // Handle the end_of_early_data alert.
+  static const uint8_t kEndOfEarlyData[2] = {SSL3_AL_WARNING,
+                                             TLS1_AD_END_OF_EARLY_DATA};
+  if (is_early_data_read && type == SSL3_RT_ALERT && body == kEndOfEarlyData) {
+    // Stop accepting early data.
+    ssl->s3->hs->can_early_read = false;
+    return ssl_open_record_discard;
+  }
+
+  if (type != SSL3_RT_APPLICATION_DATA) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
-    return -1;
+    *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
+    return ssl_open_record_error;
   }
 
-  if (rr->length != 1 || rr->data[0] != SSL3_MT_CCS) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_CHANGE_CIPHER_SPEC);
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-    return -1;
+  if (is_early_data_read) {
+    if (body.size() > kMaxEarlyDataAccepted - ssl->s3->hs->early_data_read) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_TOO_MUCH_READ_EARLY_DATA);
+      *out_alert = SSL3_AD_UNEXPECTED_MESSAGE;
+      return ssl_open_record_error;
+    }
+
+    ssl->s3->hs->early_data_read += body.size();
   }
 
-  ssl_do_msg_callback(ssl, 0 /* read */, SSL3_RT_CHANGE_CIPHER_SPEC,
-                      MakeSpan(rr->data, rr->length));
+  if (body.empty()) {
+    return ssl_open_record_discard;
+  }
 
-  rr->length = 0;
-  ssl_read_buffer_discard(ssl);
-  return 1;
+  *out = body;
+  return ssl_open_record_success;
 }
 
-void ssl3_read_close_notify(SSL *ssl) {
-  // Read records until an error or close_notify.
-  while (ssl3_get_record(ssl) > 0) {
-    ;
+ssl_open_record_t ssl3_open_change_cipher_spec(SSL *ssl, size_t *out_consumed,
+                                               uint8_t *out_alert,
+                                               Span<uint8_t> in) {
+  uint8_t type;
+  Span<uint8_t> body;
+  auto ret = tls_open_record(ssl, &type, &body, out_consumed, out_alert, in);
+  if (ret != ssl_open_record_success) {
+    return ret;
   }
+
+  if (type != SSL3_RT_CHANGE_CIPHER_SPEC) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
+    *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
+    return ssl_open_record_error;
+  }
+
+  if (body.size() != 1 || body[0] != SSL3_MT_CCS) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_CHANGE_CIPHER_SPEC);
+    *out_alert = SSL_AD_ILLEGAL_PARAMETER;
+    return ssl_open_record_error;
+  }
+
+  ssl_do_msg_callback(ssl, 0 /* read */, SSL3_RT_CHANGE_CIPHER_SPEC, body);
+  return ssl_open_record_success;
+}
+
+ssl_open_record_t ssl3_open_close_notify(SSL *ssl, size_t *out_consumed,
+                                         uint8_t *out_alert, Span<uint8_t> in) {
+  // TODO(davidben): Replace this with open_app_data so we actually process
+  // various bad behaviors.
+  uint8_t type;
+  Span<uint8_t> body;
+  auto ret = tls_open_record(ssl, &type, &body, out_consumed, out_alert, in);
+  if (ret == ssl_open_record_success) {
+    return ssl_open_record_discard;
+  }
+  return ret;
 }
 
 int ssl_send_alert(SSL *ssl, int level, int desc) {
diff --git a/ssl/ssl_buffer.cc b/ssl/ssl_buffer.cc
index 79f0cae..412df73 100644
--- a/ssl/ssl_buffer.cc
+++ b/ssl/ssl_buffer.cc
@@ -202,6 +202,46 @@
   clear_buffer(&ssl->s3->read_buffer);
 }
 
+int ssl_handle_open_record(SSL *ssl, bool *out_retry, ssl_open_record_t ret,
+                           size_t consumed, uint8_t alert) {
+  *out_retry = false;
+  if (ret != ssl_open_record_partial) {
+    ssl_read_buffer_consume(ssl, consumed);
+  }
+  if (ret != ssl_open_record_success) {
+    // Nothing was returned to the caller, so discard anything marked consumed.
+    ssl_read_buffer_discard(ssl);
+  }
+  switch (ret) {
+    case ssl_open_record_success:
+      return 1;
+
+    case ssl_open_record_partial: {
+      int read_ret = ssl_read_buffer_extend_to(ssl, consumed);
+      if (read_ret <= 0) {
+        return read_ret;
+      }
+      *out_retry = true;
+      return 1;
+    }
+
+    case ssl_open_record_discard:
+      *out_retry = true;
+      return 1;
+
+    case ssl_open_record_close_notify:
+      return 0;
+
+    case ssl_open_record_error:
+      if (alert != 0) {
+        ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
+      }
+      return -1;
+  }
+  assert(0);
+  return -1;
+}
+
 
 int ssl_write_buffer_is_pending(const SSL *ssl) {
   return ssl->s3->write_buffer.len > 0;
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 2f5374b..8e06c49 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -907,7 +907,7 @@
   return 0;
 }
 
-static int ssl_read_impl(SSL *ssl, void *buf, int num, int peek) {
+static int ssl_read_impl(SSL *ssl, void *buf, int num, bool peek) {
   ssl_reset_error_state(ssl);
 
   if (ssl->do_handshake == NULL) {
@@ -941,25 +941,48 @@
       continue;  // Loop again. We may have begun a new handshake.
     }
 
-    bool got_handshake = false;
-    int ret = ssl->method->read_app_data(ssl, &got_handshake, (uint8_t *)buf,
-                                         num, peek);
-    if (got_handshake) {
-      continue;  // Loop again to process the handshake data.
-    }
-    if (ret > 0) {
+    if (ssl->s3->pending_app_data.empty()) {
+      uint8_t alert = SSL_AD_DECODE_ERROR;
+      size_t consumed = 0;
+      auto ret =
+          ssl->method->open_app_data(ssl, &ssl->s3->pending_app_data, &consumed,
+                                     &alert, ssl_read_buffer(ssl));
+      bool retry;
+      int bio_ret = ssl_handle_open_record(ssl, &retry, ret, consumed, alert);
+      if (bio_ret <= 0) {
+        return bio_ret;
+      }
+      if (retry) {
+        continue;
+      }
       ssl->s3->key_update_count = 0;
     }
-    return ret;
+
+    if (num <= 0) {
+      return num;
+    }
+
+    size_t todo =
+        std::min(ssl->s3->pending_app_data.size(), static_cast<size_t>(num));
+    OPENSSL_memcpy(buf, ssl->s3->pending_app_data.data(), todo);
+    if (!peek) {
+      // TODO(davidben): In DTLS, should the rest of the record be discarded?
+      // DTLS is not a stream. See https://crbug.com/boringssl/65.
+      ssl->s3->pending_app_data = ssl->s3->pending_app_data.subspan(todo);
+      if (ssl->s3->pending_app_data.empty()) {
+        ssl_read_buffer_discard(ssl);
+      }
+    }
+    return static_cast<int>(todo);
   }
 }
 
 int SSL_read(SSL *ssl, void *buf, int num) {
-  return ssl_read_impl(ssl, buf, num, 0 /* consume bytes */);
+  return ssl_read_impl(ssl, buf, num, false /* consume bytes */);
 }
 
 int SSL_peek(SSL *ssl, void *buf, int num) {
-  return ssl_read_impl(ssl, buf, num, 1 /* peek */);
+  return ssl_read_impl(ssl, buf, num, true /* peek */);
 }
 
 int SSL_write(SSL *ssl, const void *buf, int num) {
@@ -1033,8 +1056,19 @@
       return -1;
     }
   } else if (ssl->s3->read_shutdown != ssl_shutdown_close_notify) {
-    // Wait for the peer's close_notify.
-    ssl->method->read_close_notify(ssl);
+    ssl->s3->pending_app_data = Span<uint8_t>();
+    for (;;) {
+      uint8_t alert = SSL_AD_DECODE_ERROR;
+      size_t consumed = 0;
+      auto ret = ssl->method->open_close_notify(ssl, &consumed, &alert,
+                                                ssl_read_buffer(ssl));
+      bool retry;
+      int bio_ret = ssl_handle_open_record(ssl, &retry, ret, consumed, alert);
+      if (bio_ret <= 0) {
+        break;
+      }
+      assert(retry);  // open_close_notify never reports success.
+    }
     if (ssl->s3->read_shutdown != ssl_shutdown_close_notify) {
       return -1;
     }
@@ -1467,10 +1501,7 @@
 void SSL_set_read_ahead(SSL *ssl, int yes) { }
 
 int SSL_pending(const SSL *ssl) {
-  if (ssl->s3->rrec.type != SSL3_RT_APPLICATION_DATA) {
-    return 0;
-  }
-  return ssl->s3->rrec.length;
+  return static_cast<int>(ssl->s3->pending_app_data.size());
 }
 
 // Fix this so it checks all the valid key/cert options
diff --git a/ssl/tls_method.cc b/ssl/tls_method.cc
index 9cc79b5..c7352ce 100644
--- a/ssl/tls_method.cc
+++ b/ssl/tls_method.cc
@@ -86,10 +86,7 @@
 
 static bool ssl3_set_read_state(SSL *ssl, UniquePtr<SSLAEADContext> aead_ctx) {
   // Cipher changes are forbidden if the current epoch has leftover data.
-  //
-  // TODO(davidben): ssl->s3->rrec.length should be impossible now. Remove it
-  // once it is only used for application data.
-  if (ssl->s3->rrec.length != 0 || tls_has_unprocessed_handshake_data(ssl)) {
+  if (tls_has_unprocessed_handshake_data(ssl)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFERED_MESSAGES_ON_CIPHER_CHANGE);
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
     return false;
@@ -115,11 +112,11 @@
     ssl3_new,
     ssl3_free,
     ssl3_get_message,
-    ssl3_read_message,
     ssl3_next_message,
-    ssl3_read_app_data,
-    ssl3_read_change_cipher_spec,
-    ssl3_read_close_notify,
+    ssl3_open_handshake,
+    ssl3_open_change_cipher_spec,
+    ssl3_open_app_data,
+    ssl3_open_close_notify,
     ssl3_write_app_data,
     ssl3_dispatch_alert,
     ssl3_supports_cipher,