Factor out the buffering and low-level record code.

This begins decoupling the transport from the SSL state machine. The buffering
logic is hidden behind an opaque API. Fields like ssl->packet and
ssl->packet_length are gone.

ssl3_get_record and dtls1_get_record now call low-level tls_open_record and
dtls_open_record functions that unpack a single record independent of who owns
the buffer. Both may be called in-place. This removes ssl->rstate which was
redundant with the buffer length.

Future work will push the buffer up the stack until it is above the handshake.
Then we can expose SSL_open and SSL_seal APIs which act like *_open_record but
return a slightly larger enum due to other events being possible. Likewise the
handshake state machine will be detached from its buffer. The existing
SSL_read, SSL_write, etc., APIs will be implemented on top of SSL_open, etc.,
combined with ssl_read_buffer_* and ssl_write_buffer_*. (Which is why
ssl_read_buffer_extend still tries to abstract between TLS's and DTLS's fairly
different needs.)

The new buffering logic does not support read-ahead (removed previously) since
it lacks a memmove on ssl_read_buffer_discard for TLS, but this could be added
if desired. The old buffering logic wasn't quite right anyway; it tried to
avoid the memmove in some cases and could get stuck too far into the buffer and
not accept records. (The only time the memmove is optional is in DTLS or if
enough of the record header is available to know that the entire next record
would fit in the buffer.)

The new logic also now actually decrypts the ciphertext in-place again, rather
than almost in-place when there's an explicit nonce/IV. (That accidentally
switched in https://boringssl-review.googlesource.com/#/c/4792/; see
3d59e04bce96474099ba76786a2337e99ae14505.)

BUG=468889

Change-Id: I403c1626253c46897f47c7ae93aeab1064b767b2
Reviewed-on: https://boringssl-review.googlesource.com/5715
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/bytestring/cbs.c b/crypto/bytestring/cbs.c
index b8caedd..c0fb677 100644
--- a/crypto/bytestring/cbs.c
+++ b/crypto/bytestring/cbs.c
@@ -137,6 +137,15 @@
   return 1;
 }
 
+int CBS_copy_bytes(CBS *cbs, uint8_t *out, size_t len) {
+  const uint8_t *v;
+  if (!cbs_get(cbs, &v, len)) {
+    return 0;
+  }
+  memcpy(out, v, len);
+  return 1;
+}
+
 static int cbs_get_length_prefixed(CBS *cbs, CBS *out, size_t len_len) {
   uint32_t len;
   if (!cbs_get_u(cbs, &len, len_len)) {
diff --git a/include/openssl/bytestring.h b/include/openssl/bytestring.h
index 4fceeaa..f6db950 100644
--- a/include/openssl/bytestring.h
+++ b/include/openssl/bytestring.h
@@ -99,6 +99,10 @@
  * |cbs|. It returns one on success and zero on error. */
 OPENSSL_EXPORT int CBS_get_bytes(CBS *cbs, CBS *out, size_t len);
 
+/* CBS_copy_bytes copies the next |len| bytes from |cbs| to |out| and advances
+ * |cbs|. It returns one on success and zero on error. */
+OPENSSL_EXPORT int CBS_copy_bytes(CBS *cbs, uint8_t *out, size_t len);
+
 /* CBS_get_u8_length_prefixed sets |*out| to the contents of an 8-bit,
  * length-prefixed value from |cbs| and advances |cbs| over it. It returns one
  * on success and zero on error. */
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index d6e352e..50fe9bd 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -292,6 +292,7 @@
 
 /* Protocol versions. */
 
+#define DTLS1_VERSION_MAJOR 0xfe
 #define SSL3_VERSION_MAJOR 0x03
 
 #define SSL3_VERSION 0x0300
@@ -1724,7 +1725,6 @@
   int shutdown; /* we have shut things down, 0x01 sent, 0x02
                  * for received */
   int state;    /* where we are */
-  int rstate;   /* where we are when reading */
 
   BUF_MEM *init_buf; /* buffer used during init */
   uint8_t *init_msg; /* pointer to handshake message body, set by
@@ -1732,10 +1732,6 @@
   int init_num;      /* amount read/written */
   int init_off;      /* amount read/written */
 
-  /* used internally to point at a raw packet */
-  uint8_t *packet;
-  unsigned int packet_length;
-
   struct ssl3_state_st *s3;  /* SSLv3 variables */
   struct dtls1_state_st *d1; /* DTLSv1 variables */
 
@@ -1910,12 +1906,6 @@
  * for the peer, but |SSL_read| will require the handshake to be completed. */
 OPENSSL_EXPORT int SSL_in_false_start(const SSL *s);
 
-/* The following 2 states are kept in ssl->rstate when reads fail,
- * you should not need these */
-#define SSL_ST_READ_HEADER 0xF0
-#define SSL_ST_READ_BODY 0xF1
-#define SSL_ST_READ_DONE 0xF2
-
 /* Obtain latest Finished message
  *   -- that we sent (SSL_get_finished)
  *   -- that we expected from peer (SSL_get_peer_finished).
@@ -2158,9 +2148,7 @@
 OPENSSL_EXPORT void SSL_load_error_strings(void);
 
 OPENSSL_EXPORT const char *SSL_state_string(const SSL *s);
-OPENSSL_EXPORT const char *SSL_rstate_string(const SSL *s);
 OPENSSL_EXPORT const char *SSL_state_string_long(const SSL *s);
-OPENSSL_EXPORT const char *SSL_rstate_string_long(const SSL *s);
 OPENSSL_EXPORT long SSL_SESSION_get_time(const SSL_SESSION *s);
 OPENSSL_EXPORT long SSL_SESSION_set_time(SSL_SESSION *s, long t);
 OPENSSL_EXPORT long SSL_SESSION_get_timeout(const SSL_SESSION *s);
diff --git a/include/openssl/ssl3.h b/include/openssl/ssl3.h
index 7ff8dbd..e04412f 100644
--- a/include/openssl/ssl3.h
+++ b/include/openssl/ssl3.h
@@ -272,10 +272,6 @@
 #define SSL3_MD_CLIENT_FINISHED_CONST "\x43\x4C\x4E\x54"
 #define SSL3_MD_SERVER_FINISHED_CONST "\x53\x52\x56\x52"
 
-#define SSL3_VERSION 0x0300
-#define SSL3_VERSION_MAJOR 0x03
-#define SSL3_VERSION_MINOR 0x00
-
 #define SSL3_RT_CHANGE_CIPHER_SPEC 20
 #define SSL3_RT_ALERT 21
 #define SSL3_RT_HANDSHAKE 22
@@ -311,16 +307,17 @@
   /* data is a non-owning pointer to the record contents. The total length of
    * the buffer is |off| + |length|. */
   uint8_t *data;
-  /* epoch, in DTLS, is the epoch number of the record. */
-  uint16_t epoch;
 } SSL3_RECORD;
 
 typedef struct ssl3_buffer_st {
-  uint8_t *buf;       /* at least SSL3_RT_MAX_PACKET_SIZE bytes, see
-                         ssl3_setup_buffers() */
-  size_t len;         /* buffer size */
-  int offset;         /* where to 'copy from' */
-  int left;           /* how many bytes left */
+  /* buf is the memory allocated for this buffer. */
+  uint8_t *buf;
+  /* offset is the offset into |buf| which the buffer contents start at. */
+  uint16_t offset;
+  /* len is the length of the buffer contents from |buf| + |offset|. */
+  uint16_t len;
+  /* cap is how much memory beyond |buf| + |offset| is available. */
+  uint16_t cap;
 } SSL3_BUFFER;
 
 #define SSL3_CT_RSA_SIGN 1
@@ -352,9 +349,6 @@
   /* flags for countermeasure against known-IV weakness */
   int need_record_splitting;
 
-  /* The value of 'extra' when the buffers were initialized */
-  int init_extra;
-
   /* have_version is true if the connection's final version is known. Otherwise
    * the version has not been negotiated yet. */
   char have_version;
@@ -363,8 +357,10 @@
    * completed. */
   char initial_handshake_complete;
 
-  SSL3_BUFFER rbuf; /* read IO goes into here */
-  SSL3_BUFFER wbuf; /* write IO goes into here */
+  /* read_buffer holds data from the transport to be processed. */
+  SSL3_BUFFER read_buffer;
+  /* write_buffer holds data to be written to the transport. */
+  SSL3_BUFFER write_buffer;
 
   SSL3_RECORD rrec; /* each decoded record goes in here */
 
diff --git a/ssl/CMakeLists.txt b/ssl/CMakeLists.txt
index 856a5af..b2fb066 100644
--- a/ssl/CMakeLists.txt
+++ b/ssl/CMakeLists.txt
@@ -13,6 +13,7 @@
   d1_pkt.c
   d1_srtp.c
   d1_srvr.c
+  dtls_record.c
   s3_both.c
   s3_clnt.c
   s3_enc.c
@@ -23,6 +24,7 @@
   ssl_aead_ctx.c
   ssl_algs.c
   ssl_asn1.c
+  ssl_buffer.c
   ssl_cert.c
   ssl_cipher.c
   ssl_lib.c
@@ -32,6 +34,7 @@
   ssl_txt.c
   t1_enc.c
   t1_lib.c
+  tls_record.c
 
   $<TARGET_OBJECTS:pqueue>
 )
diff --git a/ssl/d1_pkt.c b/ssl/d1_pkt.c
index 308d695..6681490 100644
--- a/ssl/d1_pkt.c
+++ b/ssl/d1_pkt.c
@@ -122,221 +122,75 @@
 #include "internal.h"
 
 
-/* to_u64_be treats |in| as a 8-byte big-endian integer and returns the value as
- * a |uint64_t|. */
-static uint64_t to_u64_be(const uint8_t in[8]) {
-  uint64_t ret = 0;
-  unsigned i;
-  for (i = 0; i < 8; i++) {
-    ret <<= 8;
-    ret |= in[i];
-  }
-  return ret;
-}
-
-/* dtls1_bitmap_should_discard returns one if |seq_num| has been seen in |bitmap|
- * or is stale. Otherwise it returns zero. */
-static int dtls1_bitmap_should_discard(DTLS1_BITMAP *bitmap,
-                                       const uint8_t seq_num[8]) {
-  const unsigned kWindowSize = sizeof(bitmap->map) * 8;
-
-  uint64_t seq_num_u = to_u64_be(seq_num);
-  if (seq_num_u > bitmap->max_seq_num) {
-    return 0;
-  }
-  uint64_t idx = bitmap->max_seq_num - seq_num_u;
-  return idx >= kWindowSize || (bitmap->map & (((uint64_t)1) << idx));
-}
-
-/* dtls1_bitmap_record updates |bitmap| to record receipt of sequence number
- * |seq_num|. It slides the window forward if needed. It is an error to call
- * this function on a stale sequence number. */
-static void dtls1_bitmap_record(DTLS1_BITMAP *bitmap,
-                                const uint8_t seq_num[8]) {
-  const unsigned kWindowSize = sizeof(bitmap->map) * 8;
-
-  uint64_t seq_num_u = to_u64_be(seq_num);
-  /* Shift the window if necessary. */
-  if (seq_num_u > bitmap->max_seq_num) {
-    uint64_t shift = seq_num_u - bitmap->max_seq_num;
-    if (shift >= kWindowSize) {
-      bitmap->map = 0;
-    } else {
-      bitmap->map <<= shift;
-    }
-    bitmap->max_seq_num = seq_num_u;
-  }
-
-  uint64_t idx = bitmap->max_seq_num - seq_num_u;
-  if (idx < kWindowSize) {
-    bitmap->map |= ((uint64_t)1) << idx;
-  }
-}
-
 static int do_dtls1_write(SSL *s, int type, const uint8_t *buf,
                           unsigned int len, enum dtls1_use_epoch_t use_epoch);
 
-/* Call this to get a new input record.
- * It will return <= 0 if more data is needed, normally due to an error
- * or non-blocking IO.
- * When it finishes, one packet has been decoded and can be found in
- * ssl->s3->rrec.type    - is the type of record
- * ssl->s3->rrec.data,   - data
- * ssl->s3->rrec.length, - number of bytes
- *
- * used only by dtls1_read_bytes */
-int dtls1_get_record(SSL *s) {
-  uint8_t ssl_major, ssl_minor;
-  int n;
-  SSL3_RECORD *rr;
-  uint8_t *p = NULL;
-  uint16_t version;
-
-  rr = &(s->s3->rrec);
-
-  /* get something from the wire */
+/* 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. */
+static int dtls1_get_record(SSL *ssl) {
 again:
-  /* check if we have the header */
-  if ((s->rstate != SSL_ST_READ_BODY) ||
-      (s->packet_length < DTLS1_RT_HEADER_LENGTH)) {
-    n = ssl3_read_n(s, DTLS1_RT_HEADER_LENGTH, 0);
-    /* read timeout is handled by dtls1_read_bytes */
-    if (n <= 0) {
-      return n; /* error or non-blocking */
+  /* Read a new packet if there is no unconsumed one. */
+  if (ssl_read_buffer_len(ssl) == 0) {
+    int ret = ssl_read_buffer_extend_to(ssl, 0 /* unused */);
+    if (ret <= 0) {
+      return ret;
     }
-
-    /* this packet contained a partial record, dump it */
-    if (s->packet_length != DTLS1_RT_HEADER_LENGTH) {
-      s->packet_length = 0;
-      goto again;
-    }
-
-    s->rstate = SSL_ST_READ_BODY;
-
-    p = s->packet;
-
-    if (s->msg_callback) {
-      s->msg_callback(0, 0, SSL3_RT_HEADER, p, DTLS1_RT_HEADER_LENGTH, s,
-                      s->msg_callback_arg);
-    }
-
-    /* Pull apart the header into the DTLS1_RECORD */
-    rr->type = *(p++);
-    ssl_major = *(p++);
-    ssl_minor = *(p++);
-    version = (((uint16_t)ssl_major) << 8) | ssl_minor;
-
-    /* sequence number is 64 bits, with top 2 bytes = epoch */
-    n2s(p, rr->epoch);
-
-    memcpy(&(s->s3->read_sequence[2]), p, 6);
-    p += 6;
-
-    n2s(p, rr->length);
-
-    /* Check the header. */
-    if ((s->s3->have_version && version != s->version) ||
-        (version & 0xff00) != (s->version & 0xff00) ||
-        rr->length > SSL3_RT_MAX_ENCRYPTED_LENGTH) {
-      /* The record's header is invalid, so silently drop it.
-       *
-       * TODO(davidben): This doesn't work. The DTLS record layer is not
-       * packet-based, so the remainder of the packet isn't dropped and we
-       * get a framing error. It's also unclear what it means to silently
-       * drop a record in a packet containing two records. */
-      rr->length = 0;
-      s->packet_length = 0;
-      goto again;
-    }
-
-    /* now s->rstate == SSL_ST_READ_BODY */
   }
+  assert(ssl_read_buffer_len(ssl) > 0);
 
-  /* s->rstate == SSL_ST_READ_BODY, get and decode the data */
-
-  if (rr->length > s->packet_length - DTLS1_RT_HEADER_LENGTH) {
-    /* now s->packet_length == DTLS1_RT_HEADER_LENGTH */
-    n = ssl3_read_n(s, rr->length, 1);
-    /* This packet contained a partial record, dump it. */
-    if (n != rr->length) {
-      rr->length = 0;
-      s->packet_length = 0;
-      goto again;
-    }
-
-    /* now n == rr->length,
-     * and s->packet_length == DTLS1_RT_HEADER_LENGTH + rr->length */
-  }
-  s->rstate = SSL_ST_READ_HEADER; /* set state for later operations */
-
-  if (rr->epoch != s->d1->r_epoch) {
-    /* This record is from the wrong epoch. If it is the next epoch, it could be
-     * buffered. For simplicity, drop it and expect retransmit to handle it
-     * later; DTLS is supposed to handle packet loss. */
-    rr->length = 0;
-    s->packet_length = 0;
+  /* Ensure the packet is large enough to decrypt in-place. */
+  if (ssl_read_buffer_len(ssl) < ssl_record_prefix_len(ssl)) {
+    ssl_read_buffer_clear(ssl);
     goto again;
   }
 
-  /* Check whether this is a repeat, or aged record. */
-  if (dtls1_bitmap_should_discard(&s->d1->bitmap, s->s3->read_sequence)) {
-    rr->length = 0;
-    s->packet_length = 0; /* dump this record */
-    goto again;           /* get another record */
+  uint8_t *out = ssl_read_buffer(ssl) + ssl_record_prefix_len(ssl);
+  size_t max_out = ssl_read_buffer_len(ssl) - ssl_record_prefix_len(ssl);
+  uint8_t type, alert;
+  size_t len, consumed;
+  switch (dtls_open_record(ssl, &type, out, &len, &consumed, &alert, max_out,
+                           ssl_read_buffer(ssl), ssl_read_buffer_len(ssl))) {
+    case ssl_open_record_success:
+      ssl_read_buffer_consume(ssl, consumed);
+
+      /* Discard empty records.
+       * TODO(davidben): This logic should be moved to a higher level. See
+       * https://crbug.com/521840.
+       * TODO(davidben): Limit the number of empty records as in TLS? This is
+       * useful if we also limit discarded packets. */
+      if (len == 0) {
+        goto again;
+      }
+
+      if (len > 0xffff) {
+        OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
+        return -1;
+      }
+
+      SSL3_RECORD *rr = &ssl->s3->rrec;
+      rr->type = type;
+      rr->length = (uint16_t)len;
+      rr->off = 0;
+      rr->data = out;
+      return 1;
+
+    case ssl_open_record_discard:
+      ssl_read_buffer_consume(ssl, consumed);
+      goto again;
+
+    case ssl_open_record_error:
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
+      return -1;
+
+    case ssl_open_record_partial:
+      /* Impossible in DTLS. */
+      break;
   }
 
-  /* |rr->data| points to |rr->length| bytes of ciphertext in |s->packet|. */
-  rr->data = &s->packet[DTLS1_RT_HEADER_LENGTH];
-
-  uint8_t seq[8];
-  seq[0] = rr->epoch >> 8;
-  seq[1] = rr->epoch & 0xff;
-  memcpy(&seq[2], &s->s3->read_sequence[2], 6);
-
-  /* Decrypt the packet in-place. Note it is important that |ssl_aead_ctx_open|
-   * not write beyond |rr->length|. There may be another record in the packet.
-   *
-   * TODO(davidben): This assumes |s->version| is the same as the record-layer
-   * version which isn't always true, but it only differs with the NULL cipher
-   * which ignores the parameter. */
-  size_t plaintext_len;
-  if (!SSL_AEAD_CTX_open(s->aead_read_ctx, rr->data, &plaintext_len, rr->length,
-                         rr->type, s->version, seq, rr->data, rr->length)) {
-    /* Bad packets are silently dropped in DTLS. Clear the error queue of any
-     * errors decryption may have added. */
-    ERR_clear_error();
-    rr->length = 0;
-    s->packet_length = 0;
-    goto again;
-  }
-
-  if (plaintext_len > SSL3_RT_MAX_PLAIN_LENGTH) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DATA_LENGTH_TOO_LONG);
-    ssl3_send_alert(s, SSL3_AL_FATAL, SSL_AD_RECORD_OVERFLOW);
-    return -1;
-  }
-  assert(plaintext_len < (1u << 16));
-  rr->length = plaintext_len;
-
-  rr->off = 0;
-  /* So at this point the following is true
-   * ssl->s3->rrec.type   is the type of record
-   * ssl->s3->rrec.length == number of bytes in record
-   * ssl->s3->rrec.off  == offset to first valid byte
-   * ssl->s3->rrec.data == the first byte of the record body. */
-
-  /* we have pulled in a full packet so zero things */
-  s->packet_length = 0;
-
-  dtls1_bitmap_record(&s->d1->bitmap, s->s3->read_sequence);
-
-  /* just read a 0 length packet
-   * TODO(davidben): Reject excess 0-length packets? */
-  if (rr->length == 0) {
-    goto again;
-  }
-
-  return 1;
+  assert(0);
+  OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+  return -1;
 }
 
 int dtls1_read_app_data(SSL *ssl, uint8_t *buf, int len, int peek) {
@@ -415,7 +269,7 @@
   }
 
   /* get new packet if necessary */
-  if (rr->length == 0 || s->rstate == SSL_ST_READ_BODY) {
+  if (rr->length == 0) {
     ret = dtls1_get_record(s);
     if (ret <= 0) {
       ret = dtls1_read_failed(s, ret);
@@ -477,8 +331,9 @@
       rr->length -= n;
       rr->off += n;
       if (rr->length == 0) {
-        s->rstate = SSL_ST_READ_HEADER;
         rr->off = 0;
+        /* The record has been consumed, so we may now clear the buffer. */
+        ssl_read_buffer_discard(s);
       }
     }
 
@@ -663,73 +518,11 @@
   return i;
 }
 
-/* dtls1_seal_record seals a new record of type |type| and plaintext |in| and
- * writes it to |out|. At most |max_out| bytes will be written. It returns one
- * on success and zero on error. On success, it updates the write sequence
- * number. */
-static int dtls1_seal_record(SSL *s, uint8_t *out, size_t *out_len,
-                             size_t max_out, uint8_t type, const uint8_t *in,
-                             size_t in_len, enum dtls1_use_epoch_t use_epoch) {
-  if (max_out < DTLS1_RT_HEADER_LENGTH) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);
-    return 0;
-  }
-
-  /* Determine the parameters for the current epoch. */
-  uint16_t epoch = s->d1->w_epoch;
-  SSL_AEAD_CTX *aead = s->aead_write_ctx;
-  uint8_t *seq = s->s3->write_sequence;
-  if (use_epoch == dtls1_use_previous_epoch) {
-    /* DTLS renegotiation is unsupported, so only epochs 0 (NULL cipher) and 1
-     * (negotiated cipher) exist. */
-    assert(s->d1->w_epoch == 1);
-    epoch = s->d1->w_epoch - 1;
-    aead = NULL;
-    seq = s->d1->last_write_sequence;
-  }
-
-  out[0] = type;
-
-  uint16_t wire_version = s->s3->have_version ? s->version : DTLS1_VERSION;
-  out[1] = wire_version >> 8;
-  out[2] = wire_version & 0xff;
-
-  out[3] = epoch >> 8;
-  out[4] = epoch & 0xff;
-  memcpy(&out[5], &seq[2], 6);
-
-  size_t ciphertext_len;
-  if (!SSL_AEAD_CTX_seal(aead, out + DTLS1_RT_HEADER_LENGTH, &ciphertext_len,
-                         max_out - DTLS1_RT_HEADER_LENGTH, type, wire_version,
-                         &out[3] /* seq */, in, in_len) ||
-      !ssl3_record_sequence_update(&seq[2], 6)) {
-    return 0;
-  }
-
-  if (ciphertext_len >= 1 << 16) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
-    return 0;
-  }
-  out[11] = ciphertext_len >> 8;
-  out[12] = ciphertext_len & 0xff;
-
-  *out_len = DTLS1_RT_HEADER_LENGTH + ciphertext_len;
-
-  if (s->msg_callback) {
-    s->msg_callback(1 /* write */, 0, SSL3_RT_HEADER, out,
-                    DTLS1_RT_HEADER_LENGTH, s, s->msg_callback_arg);
-  }
-
-  return 1;
-}
-
 static int do_dtls1_write(SSL *s, int type, const uint8_t *buf,
                           unsigned int len, enum dtls1_use_epoch_t use_epoch) {
-  SSL3_BUFFER *wb = &s->s3->wbuf;
-
   /* ssl3_write_pending drops the write if |BIO_write| fails in DTLS, so there
    * is never pending data. */
-  assert(s->s3->wbuf.left == 0);
+  assert(!ssl_write_buffer_is_pending(s));
 
   /* If we have an alert to send, lets send it */
   if (s->s3->alert_dispatch) {
@@ -740,7 +533,8 @@
     /* if it went, fall through and send more stuff */
   }
 
-  if (wb->buf == NULL && !ssl3_setup_write_buffer(s)) {
+  if (len > SSL3_RT_MAX_PLAIN_LENGTH) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return -1;
   }
 
@@ -748,21 +542,15 @@
     return 0;
   }
 
-  /* Align the output so the ciphertext is aligned to |SSL3_ALIGN_PAYLOAD|. */
-  uintptr_t align = (uintptr_t)wb->buf + DTLS1_RT_HEADER_LENGTH;
-  align = (0 - align) & (SSL3_ALIGN_PAYLOAD - 1);
-  uint8_t *out = wb->buf + align;
-  wb->offset = align;
-  size_t max_out = wb->len - wb->offset;
-
+  size_t max_out = len + ssl_max_seal_overhead(s);
+  uint8_t *out;
   size_t ciphertext_len;
-  if (!dtls1_seal_record(s, out, &ciphertext_len, max_out, type, buf, len,
-                         use_epoch)) {
+  if (!ssl_write_buffer_init(s, &out, max_out) ||
+      !dtls_seal_record(s, out, &ciphertext_len, max_out, type, buf, len,
+                        use_epoch)) {
     return -1;
   }
-
-  /* now let's set up wb */
-  wb->left = ciphertext_len;
+  ssl_write_buffer_set_len(s, ciphertext_len);
 
   /* memorize arguments so that ssl3_write_pending can detect bad write retries
    * later */
diff --git a/ssl/dtls_record.c b/ssl/dtls_record.c
new file mode 100644
index 0000000..d3e3192
--- /dev/null
+++ b/ssl/dtls_record.c
@@ -0,0 +1,305 @@
+/* DTLS implementation written by Nagendra Modadugu
+ * (nagendra@cs.stanford.edu) for the OpenSSL project 2005. */
+/* ====================================================================
+ * Copyright (c) 1998-2005 The OpenSSL Project.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    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
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. All advertising materials mentioning features or use of this
+ *    software must display the following acknowledgment:
+ *    "This product includes software developed by the OpenSSL Project
+ *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+ *
+ * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please contact
+ *    openssl-core@openssl.org.
+ *
+ * 5. Products derived from this software may not be called "OpenSSL"
+ *    nor may "OpenSSL" appear in their names without prior written
+ *    permission of the OpenSSL Project.
+ *
+ * 6. Redistributions of any form whatsoever must retain the following
+ *    acknowledgment:
+ *    "This product includes software developed by the OpenSSL Project
+ *    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+ * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT 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.
+ * ====================================================================
+ *
+ * This product includes cryptographic software written by Eric Young
+ * (eay@cryptsoft.com).  This product includes software written by Tim
+ * Hudson (tjh@cryptsoft.com).
+ *
+ */
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+ * All rights reserved.
+ *
+ * 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:
+ * 1. Redistributions of source code must retain the copyright
+ *    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 the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *    "This product includes cryptographic software written by
+ *     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
+ *    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
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * 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
+ * [including the GNU Public Licence.] */
+
+#include <openssl/ssl.h>
+
+#include <assert.h>
+#include <string.h>
+
+#include <openssl/bytestring.h>
+#include <openssl/err.h>
+
+#include "internal.h"
+
+
+/* to_u64_be treats |in| as a 8-byte big-endian integer and returns the value as
+ * a |uint64_t|. */
+static uint64_t to_u64_be(const uint8_t in[8]) {
+  uint64_t ret = 0;
+  unsigned i;
+  for (i = 0; i < 8; i++) {
+    ret <<= 8;
+    ret |= in[i];
+  }
+  return ret;
+}
+
+/* dtls1_bitmap_should_discard returns one if |seq_num| has been seen in |bitmap|
+ * or is stale. Otherwise it returns zero. */
+static int dtls1_bitmap_should_discard(DTLS1_BITMAP *bitmap,
+                                       const uint8_t seq_num[8]) {
+  const unsigned kWindowSize = sizeof(bitmap->map) * 8;
+
+  uint64_t seq_num_u = to_u64_be(seq_num);
+  if (seq_num_u > bitmap->max_seq_num) {
+    return 0;
+  }
+  uint64_t idx = bitmap->max_seq_num - seq_num_u;
+  return idx >= kWindowSize || (bitmap->map & (((uint64_t)1) << idx));
+}
+
+/* dtls1_bitmap_record updates |bitmap| to record receipt of sequence number
+ * |seq_num|. It slides the window forward if needed. It is an error to call
+ * this function on a stale sequence number. */
+static void dtls1_bitmap_record(DTLS1_BITMAP *bitmap,
+                                const uint8_t seq_num[8]) {
+  const unsigned kWindowSize = sizeof(bitmap->map) * 8;
+
+  uint64_t seq_num_u = to_u64_be(seq_num);
+  /* Shift the window if necessary. */
+  if (seq_num_u > bitmap->max_seq_num) {
+    uint64_t shift = seq_num_u - bitmap->max_seq_num;
+    if (shift >= kWindowSize) {
+      bitmap->map = 0;
+    } else {
+      bitmap->map <<= shift;
+    }
+    bitmap->max_seq_num = seq_num_u;
+  }
+
+  uint64_t idx = bitmap->max_seq_num - seq_num_u;
+  if (idx < kWindowSize) {
+    bitmap->map |= ((uint64_t)1) << idx;
+  }
+}
+
+enum ssl_open_record_t dtls_open_record(
+    SSL *ssl, uint8_t *out_type, uint8_t *out, size_t *out_len,
+    size_t *out_consumed, uint8_t *out_alert, size_t max_out, const uint8_t *in,
+    size_t in_len) {
+  CBS cbs;
+  CBS_init(&cbs, in, in_len);
+
+  /* Decode the record. */
+  uint8_t type;
+  uint16_t version;
+  uint8_t sequence[8];
+  CBS body;
+  if (!CBS_get_u8(&cbs, &type) ||
+      !CBS_get_u16(&cbs, &version) ||
+      !CBS_copy_bytes(&cbs, sequence, 8) ||
+      !CBS_get_u16_length_prefixed(&cbs, &body) ||
+      (ssl->s3->have_version && version != ssl->version) ||
+      (version >> 8) != DTLS1_VERSION_MAJOR ||
+      CBS_len(&body) > SSL3_RT_MAX_ENCRYPTED_LENGTH) {
+    /* The record header was incomplete or malformed. Drop the entire packet. */
+    *out_consumed = in_len;
+    return ssl_open_record_discard;
+  }
+
+  if (ssl->msg_callback != NULL) {
+    ssl->msg_callback(0 /* read */, 0, SSL3_RT_HEADER, in,
+                      DTLS1_RT_HEADER_LENGTH, ssl, ssl->msg_callback_arg);
+  }
+
+  uint16_t epoch = (((uint16_t)sequence[0]) << 8) | sequence[1];
+  if (epoch != ssl->d1->r_epoch ||
+      dtls1_bitmap_should_discard(&ssl->d1->bitmap, sequence)) {
+    /* Drop this record. It's from the wrong epoch or is a replay. Note that if
+     * |epoch| is the next epoch, the record could be buffered for later. For
+     * simplicity, drop it and expect retransmit to handle it later; DTLS must
+     * handle packet loss anyway. */
+    *out_consumed = in_len - CBS_len(&cbs);
+    return ssl_open_record_discard;
+  }
+
+  /* Decrypt the body. */
+  size_t plaintext_len;
+  if (!SSL_AEAD_CTX_open(ssl->aead_read_ctx, out, &plaintext_len, max_out,
+                         type, version, sequence, CBS_data(&body),
+                         CBS_len(&body))) {
+    /* Bad packets are silently dropped in DTLS. See section 4.2.1 of RFC 6347.
+     * Clear the error queue of any errors decryption may have added. Drop the
+     * entire packet as it must not have come from the peer.
+     *
+     * TODO(davidben): This doesn't distinguish malloc failures from encryption
+     * failures. */
+    ERR_clear_error();
+    *out_consumed = in_len - CBS_len(&cbs);
+    return ssl_open_record_discard;
+  }
+
+  /* Check the plaintext length. */
+  if (plaintext_len > SSL3_RT_MAX_PLAIN_LENGTH) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DATA_LENGTH_TOO_LONG);
+    *out_alert = SSL_AD_RECORD_OVERFLOW;
+    return ssl_open_record_error;
+  }
+
+  dtls1_bitmap_record(&ssl->d1->bitmap, sequence);
+
+  *out_type = type;
+  *out_len = plaintext_len;
+  *out_consumed = in_len - CBS_len(&cbs);
+  return ssl_open_record_success;
+}
+
+int dtls_seal_record(SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
+                     uint8_t type, const uint8_t *in, size_t in_len,
+                     enum dtls1_use_epoch_t use_epoch) {
+  /* Determine the parameters for the current epoch. */
+  uint16_t epoch = ssl->d1->w_epoch;
+  SSL_AEAD_CTX *aead = ssl->aead_write_ctx;
+  uint8_t *seq = ssl->s3->write_sequence;
+  if (use_epoch == dtls1_use_previous_epoch) {
+    /* DTLS renegotiation is unsupported, so only epochs 0 (NULL cipher) and 1
+     * (negotiated cipher) exist. */
+    assert(ssl->d1->w_epoch == 1);
+    epoch = ssl->d1->w_epoch - 1;
+    aead = NULL;
+    seq = ssl->d1->last_write_sequence;
+  }
+
+  if (max_out < DTLS1_RT_HEADER_LENGTH) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);
+    return 0;
+  }
+  /* Check the record header does not alias any part of the input.
+   * |SSL_AEAD_CTX_seal| will internally enforce other aliasing requirements. */
+  if (in < out + DTLS1_RT_HEADER_LENGTH && out < in + in_len) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_OUTPUT_ALIASES_INPUT);
+    return 0;
+  }
+
+  out[0] = type;
+
+  uint16_t wire_version = ssl->s3->have_version ? ssl->version : DTLS1_VERSION;
+  out[1] = wire_version >> 8;
+  out[2] = wire_version & 0xff;
+
+  out[3] = epoch >> 8;
+  out[4] = epoch & 0xff;
+  memcpy(&out[5], &seq[2], 6);
+
+  size_t ciphertext_len;
+  if (!SSL_AEAD_CTX_seal(aead, out + DTLS1_RT_HEADER_LENGTH, &ciphertext_len,
+                         max_out - DTLS1_RT_HEADER_LENGTH, type, wire_version,
+                         &out[3] /* seq */, in, in_len) ||
+      !ssl3_record_sequence_update(&seq[2], 6)) {
+    return 0;
+  }
+
+  if (ciphertext_len >= 1 << 16) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
+    return 0;
+  }
+  out[11] = ciphertext_len >> 8;
+  out[12] = ciphertext_len & 0xff;
+
+  *out_len = DTLS1_RT_HEADER_LENGTH + ciphertext_len;
+
+  if (ssl->msg_callback) {
+    ssl->msg_callback(1 /* write */, 0, SSL3_RT_HEADER, out,
+                      DTLS1_RT_HEADER_LENGTH, ssl, ssl->msg_callback_arg);
+  }
+
+  return 1;
+}
diff --git a/ssl/internal.h b/ssl/internal.h
index 6781e26..8dc3068 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -264,6 +264,11 @@
  * communicate a psk_identity_hint, so it is optional. */
 int ssl_cipher_requires_server_key_exchange(const SSL_CIPHER *cipher);
 
+/* ssl_cipher_get_record_split_len, for TLS 1.0 CBC mode ciphers, returns the
+ * length of an encrypted 1-byte record, for use in record-splitting. Otherwise
+ * it returns zero. */
+size_t ssl_cipher_get_record_split_len(const SSL_CIPHER *cipher);
+
 
 /* Encryption layer. */
 
@@ -348,6 +353,95 @@
 } DTLS1_BITMAP;
 
 
+/* Record layer. */
+
+/* ssl_record_prefix_len returns the length of the prefix before the ciphertext
+ * of a record for |ssl|.
+ *
+ * TODO(davidben): Expose this as part of public API once the high-level
+ * buffer-free APIs are available. */
+size_t ssl_record_prefix_len(const SSL *ssl);
+
+enum ssl_open_record_t {
+  ssl_open_record_success,
+  ssl_open_record_discard,
+  ssl_open_record_partial,
+  ssl_open_record_error,
+};
+
+/* tls_open_record decrypts a record from |in|.
+ *
+ * On success, it returns |ssl_open_record_success|. It sets |*out_type| to the
+ * record type, |*out_len| to the plaintext length, and writes the record body
+ * to |out|. It sets |*out_consumed| to the number of bytes of |in| consumed.
+ * Note that |*out_len| may be zero.
+ *
+ * If a record was successfully processed but should be discarded, it returns
+ * |ssl_open_record_discard| and sets |*out_consumed| to the number of bytes
+ * consumed.
+ *
+ * If the input did not contain a complete record, it returns
+ * |ssl_open_record_partial|. It sets |*out_consumed| to the total number of
+ * bytes necessary. It is guaranteed that a successful call to |tls_open_record|
+ * will consume at least that many bytes.
+ *
+ * On failure, it returns |ssl_open_record_error| and sets |*out_alert| to an
+ * alert to emit.
+ *
+ * If |in| and |out| alias, |out| must be <= |in| + |ssl_record_prefix_len|. */
+enum ssl_open_record_t tls_open_record(
+    SSL *ssl, uint8_t *out_type, uint8_t *out, size_t *out_len,
+    size_t *out_consumed, uint8_t *out_alert, size_t max_out, const uint8_t *in,
+    size_t in_len);
+
+/* dtls_open_record implements |tls_open_record| for DTLS. It never returns
+ * |ssl_open_record_partial| but otherwise behaves analogously. */
+enum ssl_open_record_t dtls_open_record(
+    SSL *ssl, uint8_t *out_type, uint8_t *out, size_t *out_len,
+    size_t *out_consumed, uint8_t *out_alert, size_t max_out, const uint8_t *in,
+    size_t in_len);
+
+/* ssl_seal_prefix_len returns the length of the prefix before the ciphertext
+ * when sealing a record with |ssl|. Note that this value may differ from
+ * |ssl_record_prefix_len| when TLS 1.0 CBC record-splitting is enabled. Sealing
+ * a small record may also result in a smaller output than this value.
+ *
+ * TODO(davidben): Expose this as part of public API once the high-level
+ * buffer-free APIs are available. */
+size_t ssl_seal_prefix_len(const SSL *ssl);
+
+/* ssl_max_seal_overhead returns the maximum overhead of sealing a record with
+ * |ssl|. This includes |ssl_seal_prefix_len|.
+ *
+ * TODO(davidben): Expose this as part of public API once the high-level
+ * buffer-free APIs are available. */
+size_t ssl_max_seal_overhead(const SSL *ssl);
+
+/* tls_seal_record seals a new record of type |type| and body |in| and writes it
+ * to |out|. At most |max_out| bytes will be written. It returns one on success
+ * and zero on error. If enabled, |tls_seal_record| implements TLS 1.0 CBC 1/n-1
+ * record splitting and may write two records concatenated.
+ *
+ * For a large record, the ciphertext will begin |ssl_seal_prefix_len| bytes
+ * into out. Aligning |out| appropriately may improve performance. It writes at
+ * most |in_len| + |ssl_max_seal_overhead| bytes to |out|.
+ *
+ * If |in| and |out| alias, |out| + |ssl_seal_prefix_len| must be <= |in|. */
+int tls_seal_record(SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
+                    uint8_t type, const uint8_t *in, size_t in_len);
+
+enum dtls1_use_epoch_t {
+  dtls1_use_previous_epoch,
+  dtls1_use_current_epoch,
+};
+
+/* dtls_seal_record implements |tls_seal_record| for DTLS. |use_epoch| selects
+ * which epoch's cipher state to use. */
+int dtls_seal_record(SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
+                     uint8_t type, const uint8_t *in, size_t in_len,
+                     enum dtls1_use_epoch_t use_epoch);
+
+
 /* Private key operations. */
 
 /* ssl_has_private_key returns one if |ssl| has a private key
@@ -424,6 +518,61 @@
 int ssl3_update_handshake_hash(SSL *ssl, const uint8_t *in, size_t in_len);
 
 
+/* Transport buffers. */
+
+/* ssl_read_buffer returns a pointer to contents of the read buffer. */
+uint8_t *ssl_read_buffer(SSL *ssl);
+
+/* ssl_read_buffer_len returns the length of the read buffer. */
+size_t ssl_read_buffer_len(const SSL *ssl);
+
+/* ssl_read_buffer_extend_to extends the read buffer to the desired length. For
+ * TLS, it reads to the end of the buffer until the buffer is |len| bytes
+ * long. For DTLS, it reads a new packet and ignores |len|. It returns one on
+ * success, zero on EOF, and a negative number on error.
+ *
+ * It is an error to call |ssl_read_buffer_extend_to| in DTLS when the buffer is
+ * non-empty. */
+int ssl_read_buffer_extend_to(SSL *ssl, size_t len);
+
+/* ssl_read_buffer_consume consumes |len| bytes from the read buffer. It
+ * advances the data pointer and decrements the length. The memory consumed will
+ * remain valid until the next call to |ssl_read_buffer_extend| or it is
+ * discarded with |ssl_read_buffer_discard|. */
+void ssl_read_buffer_consume(SSL *ssl, size_t len);
+
+/* ssl_read_buffer_discard discards the consumed bytes from the read buffer. If
+ * the buffer is now empty, it releases memory used by it. */
+void ssl_read_buffer_discard(SSL *ssl);
+
+/* ssl_read_buffer_clear releases all memory associated with the read buffer and
+ * zero-initializes it. */
+void ssl_read_buffer_clear(SSL *ssl);
+
+/* 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);
+
+/* ssl_write_buffer_init initializes the write buffer. On success, it sets
+ * |*out_ptr| to the start of the write buffer with space for up to |max_len|
+ * bytes. It returns one on success and zero on failure. Call
+ * |ssl_write_buffer_set_len| to complete initialization. */
+int ssl_write_buffer_init(SSL *ssl, uint8_t **out_ptr, size_t max_len);
+
+/* ssl_write_buffer_set_len is called after |ssl_write_buffer_init| to complete
+ * initialization after |len| bytes are written to the buffer. */
+void ssl_write_buffer_set_len(SSL *ssl, size_t len);
+
+/* ssl_write_buffer_flush flushes the write buffer to the transport. It returns
+ * one on success and <= 0 on error. For DTLS, whether or not the write
+ * succeeds, the write buffer will be cleared. */
+int ssl_write_buffer_flush(SSL *ssl);
+
+/* ssl_write_buffer_clear releases all memory associated with the write buffer
+ * and zero-initializes it. */
+void ssl_write_buffer_clear(SSL *ssl);
+
+
 /* Underdocumented functions.
  *
  * Functions below here haven't been touched up and may be underdocumented. */
@@ -922,10 +1071,6 @@
 const SSL_CIPHER *ssl3_choose_cipher(
     SSL *ssl, STACK_OF(SSL_CIPHER) *clnt,
     struct ssl_cipher_preference_list_st *srvr);
-int ssl3_setup_read_buffer(SSL *s);
-int ssl3_setup_write_buffer(SSL *s);
-int ssl3_release_read_buffer(SSL *s);
-int ssl3_release_write_buffer(SSL *s);
 
 int ssl3_new(SSL *s);
 void ssl3_free(SSL *s);
@@ -941,13 +1086,7 @@
 int ssl3_set_handshake_header(SSL *s, int htype, unsigned long len);
 int ssl3_handshake_write(SSL *s);
 
-enum dtls1_use_epoch_t {
-  dtls1_use_previous_epoch,
-  dtls1_use_current_epoch,
-};
-
 int dtls1_do_write(SSL *s, int type, enum dtls1_use_epoch_t use_epoch);
-int ssl3_read_n(SSL *s, int n, int extend);
 int dtls1_read_app_data(SSL *ssl, uint8_t *buf, int len, int peek);
 void dtls1_read_close_notify(SSL *ssl);
 int dtls1_read_bytes(SSL *s, int type, uint8_t *buf, int len, int peek);
@@ -1018,7 +1157,6 @@
 
 long dtls1_get_message(SSL *s, int st1, int stn, int mt, long max,
                        enum ssl_hash_message_t hash_message, int *ok);
-int dtls1_get_record(SSL *s);
 int dtls1_dispatch_alert(SSL *s);
 
 int ssl_init_wbio_buffer(SSL *s, int push);
diff --git a/ssl/s3_both.c b/ssl/s3_both.c
index 7580016..0978a5d 100644
--- a/ssl/s3_both.c
+++ b/ssl/s3_both.c
@@ -559,92 +559,6 @@
   return al;
 }
 
-int ssl3_setup_read_buffer(SSL *s) {
-  uint8_t *p;
-  size_t len, align = 0, headerlen;
-
-  if (SSL_IS_DTLS(s)) {
-    headerlen = DTLS1_RT_HEADER_LENGTH;
-  } else {
-    headerlen = SSL3_RT_HEADER_LENGTH;
-  }
-
-#if defined(SSL3_ALIGN_PAYLOAD) && SSL3_ALIGN_PAYLOAD != 0
-  align = (-SSL3_RT_HEADER_LENGTH) & (SSL3_ALIGN_PAYLOAD - 1);
-#endif
-
-  if (s->s3->rbuf.buf == NULL) {
-    len = SSL3_RT_MAX_ENCRYPTED_LENGTH + headerlen + align;
-    if (s->options & SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER) {
-      s->s3->init_extra = 1;
-      len += SSL3_RT_MAX_EXTRA;
-    }
-    p = OPENSSL_malloc(len);
-    if (p == NULL) {
-      goto err;
-    }
-    s->s3->rbuf.buf = p;
-    s->s3->rbuf.len = len;
-  }
-
-  s->packet = &s->s3->rbuf.buf[0];
-  return 1;
-
-err:
-  OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-  return 0;
-}
-
-int ssl3_setup_write_buffer(SSL *s) {
-  uint8_t *p;
-  size_t len, align = 0, headerlen;
-
-  if (SSL_IS_DTLS(s)) {
-    headerlen = DTLS1_RT_HEADER_LENGTH + 1;
-  } else {
-    headerlen = SSL3_RT_HEADER_LENGTH;
-  }
-
-#if defined(SSL3_ALIGN_PAYLOAD) && SSL3_ALIGN_PAYLOAD != 0
-  align = (-SSL3_RT_HEADER_LENGTH) & (SSL3_ALIGN_PAYLOAD - 1);
-#endif
-
-  if (s->s3->wbuf.buf == NULL) {
-    len = s->max_send_fragment + SSL3_RT_SEND_MAX_ENCRYPTED_OVERHEAD +
-          headerlen + align;
-    /* Account for 1/n-1 record splitting. */
-    if (s->mode & SSL_MODE_CBC_RECORD_SPLITTING) {
-      len += headerlen + align + 1 + SSL3_RT_SEND_MAX_ENCRYPTED_OVERHEAD;
-    }
-
-    p = OPENSSL_malloc(len);
-    if (p == NULL) {
-      goto err;
-    }
-    s->s3->wbuf.buf = p;
-    s->s3->wbuf.len = len;
-  }
-
-  return 1;
-
-err:
-  OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-  return 0;
-}
-
-int ssl3_release_write_buffer(SSL *s) {
-  OPENSSL_free(s->s3->wbuf.buf);
-  s->s3->wbuf.buf = NULL;
-  return 1;
-}
-
-int ssl3_release_read_buffer(SSL *s) {
-  OPENSSL_free(s->s3->rbuf.buf);
-  s->s3->rbuf.buf = NULL;
-  s->packet = NULL;
-  return 1;
-}
-
 int ssl_fill_hello_random(uint8_t *out, size_t len, int is_server) {
   if (is_server) {
     const uint32_t current_time = time(NULL);
diff --git a/ssl/s3_lib.c b/ssl/s3_lib.c
index 52b75d2..22e7990 100644
--- a/ssl/s3_lib.c
+++ b/ssl/s3_lib.c
@@ -224,8 +224,8 @@
   }
 
   ssl3_cleanup_key_block(s);
-  ssl3_release_read_buffer(s);
-  ssl3_release_write_buffer(s);
+  ssl_read_buffer_clear(s);
+  ssl_write_buffer_clear(s);
   DH_free(s->s3->tmp.dh);
   EC_KEY_free(s->s3->tmp.ecdh);
 
diff --git a/ssl/s3_pkt.c b/ssl/s3_pkt.c
index c1dfb91..5bddf98 100644
--- a/ssl/s3_pkt.c
+++ b/ssl/s3_pkt.c
@@ -120,141 +120,11 @@
 #include "internal.h"
 
 
-static int do_ssl3_write(SSL *s, int type, const uint8_t *buf, unsigned int len,
-                         char fragment);
-static int ssl3_get_record(SSL *s);
-
-int ssl3_read_n(SSL *s, int n, int extend) {
-  /* If |extend| is 0, obtain new n-byte packet;
-   * if |extend| is 1, increase packet by another n bytes.
-   *
-   * The packet will be in the sub-array of |s->s3->rbuf.buf| specified by
-   * |s->packet| and |s->packet_length|. (If DTLS and |extend| is 0, additional
-   * bytes will be read into |rbuf|, up to the size of the buffer.)
-   *
-   * TODO(davidben): |dtls1_get_record| and |ssl3_get_record| have very
-   * different needs. Separate the two record layers. In DTLS, |BIO_read| is
-   * called at most once, and only when |extend| is 0. In TLS, the buffer never
-   * contains more than one record. */
-  int i, len, left;
-  uintptr_t align = 0;
-  uint8_t *pkt;
-  SSL3_BUFFER *rb;
-
-  if (n <= 0) {
-    return n;
-  }
-
-  rb = &s->s3->rbuf;
-  if (rb->buf == NULL && !ssl3_setup_read_buffer(s)) {
-    return -1;
-  }
-
-  left = rb->left;
-
-  align = (uintptr_t)rb->buf + SSL3_RT_HEADER_LENGTH;
-  align = (0 - align) & (SSL3_ALIGN_PAYLOAD - 1);
-
-  if (!extend) {
-    /* start with empty packet ... */
-    if (left == 0) {
-      rb->offset = align;
-    } else if (align != 0 && left >= SSL3_RT_HEADER_LENGTH) {
-      /* check if next packet length is large enough to justify payload
-       * alignment... */
-      pkt = rb->buf + rb->offset;
-      if (pkt[0] == SSL3_RT_APPLICATION_DATA && (pkt[3] << 8 | pkt[4]) >= 128) {
-        /* Note that even if packet is corrupted and its length field is
-         * insane, we can only be led to wrong decision about whether memmove
-         * will occur or not. Header values has no effect on memmove arguments
-         * and therefore no buffer overrun can be triggered. */
-        memmove(rb->buf + align, pkt, left);
-        rb->offset = align;
-      }
-    }
-    s->packet = rb->buf + rb->offset;
-    s->packet_length = 0;
-    /* ... now we can act as if 'extend' was set */
-  }
-
-  /* In DTLS, if there is leftover data from the previous packet or |extend| is
-   * true, clamp to the previous read. DTLS records may not span packet
-   * boundaries. */
-  if (SSL_IS_DTLS(s) && n > left && (left > 0 || extend)) {
-    n = left;
-  }
-
-  /* if there is enough in the buffer from a previous read, take some */
-  if (left >= n) {
-    s->packet_length += n;
-    rb->left = left - n;
-    rb->offset += n;
-    return n;
-  }
-
-  /* else we need to read more data */
-
-  len = s->packet_length;
-  pkt = rb->buf + align;
-  /* Move any available bytes to front of buffer: |len| bytes already pointed
-   * to by |packet|, |left| extra ones at the end. */
-  if (s->packet != pkt) {
-    /* len > 0 */
-    memmove(pkt, s->packet, len + left);
-    s->packet = pkt;
-    rb->offset = len + align;
-  }
-
-  if (n > (int)(rb->len - rb->offset)) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
-  }
-
-  int max = n;
-  if (SSL_IS_DTLS(s) && !extend) {
-    max = rb->len - rb->offset;
-  }
-
-  while (left < n) {
-    /* Now we have len+left bytes at the front of s->s3->rbuf.buf and need to
-     * read in more until we have len+n (up to len+max if possible). */
-    ERR_clear_system_error();
-    if (s->rbio != NULL) {
-      s->rwstate = SSL_READING;
-      i = BIO_read(s->rbio, pkt + len + left, max - left);
-    } else {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_READ_BIO_NOT_SET);
-      i = -1;
-    }
-
-    if (i <= 0) {
-      rb->left = left;
-      if (len + left == 0) {
-        ssl3_release_read_buffer(s);
-      }
-      return i;
-    }
-    left += i;
-    /* reads should *never* span multiple packets for DTLS because the
-     * underlying transport protocol is message oriented as opposed to byte
-     * oriented as in the TLS case. */
-    if (SSL_IS_DTLS(s) && n > left) {
-      n = left; /* makes the while condition false */
-    }
-  }
-
-  /* done reading, now the book-keeping */
-  rb->offset += n;
-  rb->left = left - n;
-  s->packet_length += n;
-  s->rwstate = SSL_NOTHING;
-
-  return n;
-}
+static int do_ssl3_write(SSL *s, int type, const uint8_t *buf, unsigned len);
 
 /* kMaxEmptyRecords is the number of consecutive, empty records that will be
  * processed. Without this limit an attacker could send empty records at a
- * faster rate than we can process and cause |ssl3_get_record| to loop
+ * faster rate than we can process and cause record processing to loop
  * forever. */
 static const uint8_t kMaxEmptyRecords = 32;
 
@@ -262,143 +132,73 @@
  * processed. */
 static const uint8_t kMaxWarningAlerts = 4;
 
-/* Call this to get a new input record. It will return <= 0 if more data is
- * needed, normally due to an error or non-blocking IO. When it finishes, one
- * packet has been decoded and can be found in
- * ssl->s3->rrec.type    - is the type of record
- * ssl->s3->rrec.data    - data
- * ssl->s3->rrec.length  - number of bytes */
-/* used only by ssl3_read_bytes */
-static int ssl3_get_record(SSL *s) {
-  uint8_t ssl_major, ssl_minor;
-  int al, n, i, ret = -1;
-  SSL3_RECORD *rr = &s->s3->rrec;
-  uint8_t *p;
-  uint16_t version;
-  size_t extra;
-
+/* 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. */
+static int ssl3_get_record(SSL *ssl) {
+  int ret;
 again:
-  /* check if we have the header */
-  if (s->rstate != SSL_ST_READ_BODY ||
-      s->packet_length < SSL3_RT_HEADER_LENGTH) {
-    n = ssl3_read_n(s, SSL3_RT_HEADER_LENGTH, 0);
-    if (n <= 0) {
-      return n; /* error or non-blocking */
-    }
-    s->rstate = SSL_ST_READ_BODY;
+  /* Ensure the buffer is large enough to decrypt in-place. */
+  ret = ssl_read_buffer_extend_to(ssl, ssl_record_prefix_len(ssl));
+  if (ret <= 0) {
+    return ret;
+  }
+  assert(ssl_read_buffer_len(ssl) >= ssl_record_prefix_len(ssl));
 
-    /* Some bytes were read, so the read buffer must be existant and
-     * |s->s3->init_extra| is defined. */
-    assert(s->s3->rbuf.buf != NULL);
-    extra = s->s3->init_extra ? SSL3_RT_MAX_EXTRA : 0;
+  uint8_t *out = ssl_read_buffer(ssl) + ssl_record_prefix_len(ssl);
+  size_t max_out = ssl_read_buffer_len(ssl) - ssl_record_prefix_len(ssl);
+  uint8_t type, alert;
+  size_t len, consumed;
+  switch (tls_open_record(ssl, &type, out, &len, &consumed, &alert, max_out,
+                          ssl_read_buffer(ssl), ssl_read_buffer_len(ssl))) {
+    case ssl_open_record_success:
+      ssl_read_buffer_consume(ssl, consumed);
 
-    p = s->packet;
-    if (s->msg_callback) {
-      s->msg_callback(0, 0, SSL3_RT_HEADER, p, 5, s, s->msg_callback_arg);
-    }
+      /* Discard empty records.
+       * TODO(davidben): This logic should be moved to a higher level. See
+       * https://crbug.com/521840. */
+      if (len == 0) {
+        ssl->s3->empty_record_count++;
+        if (ssl->s3->empty_record_count > kMaxEmptyRecords) {
+          OPENSSL_PUT_ERROR(SSL, SSL_R_TOO_MANY_EMPTY_FRAGMENTS);
+          ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
+          return -1;
+        }
+        goto again;
+      }
+      ssl->s3->empty_record_count = 0;
 
-    /* Pull apart the header into the SSL3_RECORD */
-    rr->type = *(p++);
-    ssl_major = *(p++);
-    ssl_minor = *(p++);
-    version = (((uint16_t)ssl_major) << 8) | ssl_minor;
-    n2s(p, rr->length);
+      if (len > 0xffff) {
+        OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
+        return -1;
+      }
 
-    if (s->s3->have_version && version != s->version) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_VERSION_NUMBER);
-      al = SSL_AD_PROTOCOL_VERSION;
-      goto f_err;
-    }
+      SSL3_RECORD *rr = &ssl->s3->rrec;
+      rr->type = type;
+      rr->length = (uint16_t)len;
+      rr->off = 0;
+      rr->data = out;
+      return 1;
 
-    if ((version >> 8) != SSL3_VERSION_MAJOR) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_VERSION_NUMBER);
-      goto err;
-    }
+    case ssl_open_record_partial:
+      ret = ssl_read_buffer_extend_to(ssl, consumed);
+      if (ret <= 0) {
+        return ret;
+      }
+      goto again;
 
-    if (rr->length > SSL3_RT_MAX_ENCRYPTED_LENGTH + extra) {
-      al = SSL_AD_RECORD_OVERFLOW;
-      OPENSSL_PUT_ERROR(SSL, SSL_R_ENCRYPTED_LENGTH_TOO_LONG);
-      goto f_err;
-    }
+    case ssl_open_record_discard:
+      ssl_read_buffer_consume(ssl, consumed);
+      goto again;
 
-    /* now s->rstate == SSL_ST_READ_BODY */
-  } else {
-    /* |packet_length| is non-zero and |s->rstate| is |SSL_ST_READ_BODY|. The
-     * read buffer must be existant and |s->s3->init_extra| is defined. */
-    assert(s->s3->rbuf.buf != NULL);
-    extra = s->s3->init_extra ? SSL3_RT_MAX_EXTRA : 0;
+    case ssl_open_record_error:
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
+      return -1;
   }
 
-  /* s->rstate == SSL_ST_READ_BODY, get and decode the data */
-
-  if (rr->length > s->packet_length - SSL3_RT_HEADER_LENGTH) {
-    /* now s->packet_length == SSL3_RT_HEADER_LENGTH */
-    i = rr->length;
-    n = ssl3_read_n(s, i, 1);
-    if (n <= 0) {
-      /* Error or non-blocking IO. Now |n| == |rr->length|, and
-       * |s->packet_length| == |SSL3_RT_HEADER_LENGTH| + |rr->length|. */
-      return n;
-    }
-  }
-
-  s->rstate = SSL_ST_READ_HEADER; /* set state for later operations */
-
-  /* |rr->data| points to |rr->length| bytes of ciphertext in |s->packet|. */
-  rr->data = &s->packet[SSL3_RT_HEADER_LENGTH];
-
-  /* Decrypt the packet in-place.
-   *
-   * TODO(davidben): This assumes |s->version| is the same as the record-layer
-   * version which isn't always true, but it only differs with the NULL cipher
-   * which ignores the parameter. */
-  size_t plaintext_len;
-  if (!SSL_AEAD_CTX_open(s->aead_read_ctx, rr->data, &plaintext_len, rr->length,
-                         rr->type, s->version, s->s3->read_sequence, rr->data,
-                         rr->length)) {
-    al = SSL_AD_BAD_RECORD_MAC;
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC);
-    goto f_err;
-  }
-  if (!ssl3_record_sequence_update(s->s3->read_sequence, 8)) {
-    goto err;
-  }
-  if (plaintext_len > SSL3_RT_MAX_PLAIN_LENGTH + extra) {
-    al = SSL_AD_RECORD_OVERFLOW;
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DATA_LENGTH_TOO_LONG);
-    goto f_err;
-  }
-  assert(plaintext_len <= (1u << 16));
-  rr->length = plaintext_len;
-
-  rr->off = 0;
-  /* So at this point the following is true:
-   * ssl->s3->rrec.type is the type of record;
-   * ssl->s3->rrec.length is the number of bytes in the record;
-   * ssl->s3->rrec.off is the offset to first valid byte;
-   * ssl->s3->rrec.data the first byte of the record body. */
-
-  /* we have pulled in a full packet so zero things */
-  s->packet_length = 0;
-
-  /* just read a 0 length packet */
-  if (rr->length == 0) {
-    s->s3->empty_record_count++;
-    if (s->s3->empty_record_count > kMaxEmptyRecords) {
-      al = SSL_AD_UNEXPECTED_MESSAGE;
-      OPENSSL_PUT_ERROR(SSL, SSL_R_TOO_MANY_EMPTY_FRAGMENTS);
-      goto f_err;
-    }
-    goto again;
-  }
-  s->s3->empty_record_count = 0;
-
-  return 1;
-
-f_err:
-  ssl3_send_alert(s, SSL3_AL_FATAL, al);
-err:
-  return ret;
+  assert(0);
+  OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+  return -1;
 }
 
 int ssl3_write_app_data(SSL *ssl, const void *buf, int len) {
@@ -440,29 +240,18 @@
     return -1;
   }
 
-  int record_split_done = 0;
   n = (len - tot);
   for (;;) {
     /* max contains the maximum number of bytes that we can put into a
      * record. */
     unsigned max = s->max_send_fragment;
-    /* fragment is true if do_ssl3_write should send the first byte in its own
-     * record in order to randomise a CBC IV. */
-    int fragment = 0;
-    if (!record_split_done && s->s3->need_record_splitting &&
-        type == SSL3_RT_APPLICATION_DATA) {
-      /* Only the the first record per write call needs to be split. The
-       * remaining plaintext was determined before the IV was randomized. */
-      fragment = 1;
-      record_split_done = 1;
-    }
     if (n > max) {
       nw = max;
     } else {
       nw = n;
     }
 
-    i = do_ssl3_write(s, type, &buf[tot], nw, fragment);
+    i = do_ssl3_write(s, type, &buf[tot], nw);
     if (i <= 0) {
       s->s3->wnum = tot;
       return i;
@@ -478,65 +267,10 @@
   }
 }
 
-/* ssl3_seal_record seals a new record of type |type| and plaintext |in| and
- * writes it to |out|. At most |max_out| bytes will be written. It returns one
- * on success and zero on error. On success, it updates the write sequence
- * number. */
-static int ssl3_seal_record(SSL *s, uint8_t *out, size_t *out_len,
-                            size_t max_out, uint8_t type, const uint8_t *in,
-                            size_t in_len) {
-  if (max_out < SSL3_RT_HEADER_LENGTH) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);
-    return 0;
-  }
-
-  out[0] = type;
-
-  /* Some servers hang if initial ClientHello is larger than 256 bytes and
-   * record version number > TLS 1.0. */
-  uint16_t wire_version = s->version;
-  if (!s->s3->have_version && s->version > SSL3_VERSION) {
-    wire_version = TLS1_VERSION;
-  }
-  out[1] = wire_version >> 8;
-  out[2] = wire_version & 0xff;
-
-  size_t ciphertext_len;
-  if (!SSL_AEAD_CTX_seal(s->aead_write_ctx, out + SSL3_RT_HEADER_LENGTH,
-                         &ciphertext_len, max_out - SSL3_RT_HEADER_LENGTH,
-                         type, wire_version, s->s3->write_sequence, in,
-                         in_len) ||
-      !ssl3_record_sequence_update(s->s3->write_sequence, 8)) {
-    return 0;
-  }
-
-  if (ciphertext_len >= 1 << 16) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
-    return 0;
-  }
-  out[3] = ciphertext_len >> 8;
-  out[4] = ciphertext_len & 0xff;
-
-  *out_len = SSL3_RT_HEADER_LENGTH + ciphertext_len;
-
- if (s->msg_callback) {
-   s->msg_callback(1 /* write */, 0, SSL3_RT_HEADER, out, SSL3_RT_HEADER_LENGTH,
-                   s, s->msg_callback_arg);
- }
-
-  return 1;
-}
-
-/* do_ssl3_write writes an SSL record of the given type. If |fragment| is 1
- * then it splits the record into a one byte record and a record with the rest
- * of the data in order to randomise a CBC IV. */
-static int do_ssl3_write(SSL *s, int type, const uint8_t *buf, unsigned int len,
-                         char fragment) {
-  SSL3_BUFFER *wb = &s->s3->wbuf;
-
-  /* first check if there is a SSL3_BUFFER still being written out. This will
-   * happen with non blocking IO */
-  if (wb->left != 0) {
+/* do_ssl3_write writes an SSL record of the given type. */
+static int do_ssl3_write(SSL *s, int type, const uint8_t *buf, unsigned len) {
+  /* If there is still data from the previous record, flush it. */
+  if (ssl_write_buffer_is_pending(s)) {
     return ssl3_write_pending(s, type, buf, len);
   }
 
@@ -549,76 +283,40 @@
     /* if it went, fall through and send more stuff */
   }
 
-  if (wb->buf == NULL && !ssl3_setup_write_buffer(s)) {
+  if (len > SSL3_RT_MAX_PLAIN_LENGTH) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return -1;
   }
 
   if (len == 0) {
     return 0;
   }
-  if (len == 1) {
-    /* No sense in fragmenting a one-byte record. */
-    fragment = 0;
-  }
 
-  /* Align the output so the ciphertext is aligned to |SSL3_ALIGN_PAYLOAD|. */
-  uintptr_t align;
-  if (fragment) {
-    /* Only CBC-mode ciphers require fragmenting. CBC-mode ciphertext is a
-     * multiple of the block size which we may assume is aligned. Thus we only
-     * need to account for a second copy of the record header. */
-    align = (uintptr_t)wb->buf + 2 * SSL3_RT_HEADER_LENGTH;
-  } else {
-    align = (uintptr_t)wb->buf + SSL3_RT_HEADER_LENGTH;
-  }
-  align = (0 - align) & (SSL3_ALIGN_PAYLOAD - 1);
-  uint8_t *out = wb->buf + align;
-  wb->offset = align;
-  size_t max_out = wb->len - wb->offset;
-
-  const uint8_t *orig_buf = buf;
-  unsigned int orig_len = len;
-  size_t fragment_len = 0;
-  if (fragment) {
-    /* Write the first byte in its own record as a countermeasure against
-     * known-IV weaknesses in CBC ciphersuites. (See
-     * http://www.openssl.org/~bodo/tls-cbc.txt.) */
-    if (!ssl3_seal_record(s, out, &fragment_len, max_out, type, buf, 1)) {
-      return -1;
-    }
-    out += fragment_len;
-    max_out -= fragment_len;
-    buf++;
-    len--;
-  }
-
-  assert((((uintptr_t)out + SSL3_RT_HEADER_LENGTH) & (SSL3_ALIGN_PAYLOAD - 1))
-         == 0);
-  size_t ciphertext_len;
-  if (!ssl3_seal_record(s, out, &ciphertext_len, max_out, type, buf, len)) {
+  size_t max_out = len + ssl_max_seal_overhead(s);
+  if (max_out < len) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
     return -1;
   }
-  ciphertext_len += fragment_len;
-
-  /* now let's set up wb */
-  wb->left = ciphertext_len;
+  uint8_t *out;
+  size_t ciphertext_len;
+  if (!ssl_write_buffer_init(s, &out, max_out) ||
+      !tls_seal_record(s, out, &ciphertext_len, max_out, type, buf, len)) {
+    return -1;
+  }
+  ssl_write_buffer_set_len(s, ciphertext_len);
 
   /* memorize arguments so that ssl3_write_pending can detect bad write retries
    * later */
-  s->s3->wpend_tot = orig_len;
-  s->s3->wpend_buf = orig_buf;
+  s->s3->wpend_tot = len;
+  s->s3->wpend_buf = buf;
   s->s3->wpend_type = type;
-  s->s3->wpend_ret = orig_len;
+  s->s3->wpend_ret = len;
 
   /* we now just need to write the buffer */
-  return ssl3_write_pending(s, type, orig_buf, orig_len);
+  return ssl3_write_pending(s, type, buf, len);
 }
 
-/* if s->s3->wbuf.left != 0, we need to call this */
 int ssl3_write_pending(SSL *s, int type, const uint8_t *buf, unsigned int len) {
-  int i;
-  SSL3_BUFFER *wb = &(s->s3->wbuf);
-
   if (s->s3->wpend_tot > (int)len ||
       (s->s3->wpend_buf != buf &&
        !(s->mode & SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER)) ||
@@ -627,35 +325,11 @@
     return -1;
   }
 
-  for (;;) {
-    ERR_clear_system_error();
-    if (s->wbio != NULL) {
-      s->rwstate = SSL_WRITING;
-      i = BIO_write(s->wbio, (char *)&(wb->buf[wb->offset]),
-                    (unsigned int)wb->left);
-    } else {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_BIO_NOT_SET);
-      i = -1;
-    }
-    if (i == wb->left) {
-      wb->left = 0;
-      wb->offset += i;
-      ssl3_release_write_buffer(s);
-      s->rwstate = SSL_NOTHING;
-      return s->s3->wpend_ret;
-    } else if (i <= 0) {
-      if (SSL_IS_DTLS(s)) {
-        /* For DTLS, just drop it. That's kind of the whole point in
-         * using a datagram service */
-        wb->left = 0;
-      }
-      return i;
-    }
-    /* TODO(davidben): This codepath is used in DTLS, but the write
-     * payload may not split across packets. */
-    wb->offset += i;
-    wb->left -= i;
+  int ret = ssl_write_buffer_flush(s);
+  if (ret <= 0) {
+    return ret;
   }
+  return s->s3->wpend_ret;
 }
 
 /* ssl3_expect_change_cipher_spec informs the record layer that a
@@ -770,7 +444,7 @@
   rr = &s->s3->rrec;
 
   /* get new packet if necessary */
-  if (rr->length == 0 || s->rstate == SSL_ST_READ_BODY) {
+  if (rr->length == 0) {
     ret = ssl3_get_record(s);
     if (ret <= 0) {
       return ret;
@@ -834,11 +508,9 @@
       rr->length -= n;
       rr->off += n;
       if (rr->length == 0) {
-        s->rstate = SSL_ST_READ_HEADER;
         rr->off = 0;
-        if (s->s3->rbuf.left == 0) {
-          ssl3_release_read_buffer(s);
-        }
+        /* The record has been consumed, so we may now clear the buffer. */
+        ssl_read_buffer_discard(s);
       }
     }
 
@@ -897,7 +569,7 @@
      * protocol, namely in HTTPS, just before reading the HTTP response. Require
      * the record-layer be idle and avoid complexities of sending a handshake
      * record while an application_data record is being written. */
-    if (s->s3->wbuf.left != 0 || s->s3->rbuf.left != 0) {
+    if (ssl_write_buffer_is_pending(s)) {
       al = SSL_AD_NO_RENEGOTIATION;
       OPENSSL_PUT_ERROR(SSL, SSL_R_NO_RENEGOTIATION);
       goto f_err;
@@ -1100,8 +772,9 @@
   s->s3->alert_dispatch = 1;
   s->s3->send_alert[0] = level;
   s->s3->send_alert[1] = desc;
-  if (s->s3->wbuf.left == 0) {
-    /* data is still being written out. */
+  if (!ssl_write_buffer_is_pending(s)) {
+    /* Nothing is being written out, so the alert may be dispatched
+     * immediately. */
     return s->method->ssl_dispatch_alert(s);
   }
 
@@ -1115,7 +788,7 @@
   void (*cb)(const SSL *ssl, int type, int val) = NULL;
 
   s->s3->alert_dispatch = 0;
-  i = do_ssl3_write(s, SSL3_RT_ALERT, &s->s3->send_alert[0], 2, 0);
+  i = do_ssl3_write(s, SSL3_RT_ALERT, &s->s3->send_alert[0], 2);
   if (i <= 0) {
     s->s3->alert_dispatch = 1;
   } else {
diff --git a/ssl/s3_srvr.c b/ssl/s3_srvr.c
index 5a46abc..b5fa946 100644
--- a/ssl/s3_srvr.c
+++ b/ssl/s3_srvr.c
@@ -599,12 +599,12 @@
   /* 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 = ssl3_read_n(s, SSL3_RT_HEADER_LENGTH, 0 /* new packet */);
+  int ret = ssl_read_buffer_extend_to(s, SSL3_RT_HEADER_LENGTH);
   if (ret <= 0) {
     return ret;
   }
-  assert(s->packet_length == SSL3_RT_HEADER_LENGTH);
-  const uint8_t *p = s->packet;
+  assert(ssl_read_buffer_len(s) == SSL3_RT_HEADER_LENGTH);
+  const uint8_t *p = ssl_read_buffer(s);
 
   /* Some dedicated error codes for protocol mixups should the application wish
    * to interpret them differently. (These do not overlap with ClientHello or
@@ -629,15 +629,7 @@
     return 1;
   }
 
-  /* Fall through to the standard logic. Unread what's been read to re-process
-   * it. */
-  assert(s->rstate == SSL_ST_READ_HEADER);
-  assert(s->s3->rbuf.offset >= SSL3_RT_HEADER_LENGTH);
-  s->s3->rbuf.offset -= SSL3_RT_HEADER_LENGTH;
-  s->s3->rbuf.left += SSL3_RT_HEADER_LENGTH;
-  s->packet = NULL;
-  s->packet_length = 0;
-
+  /* Fall through to the standard logic. */
   s->state = SSL3_ST_SR_CLNT_HELLO_A;
   return 1;
 }
@@ -653,8 +645,8 @@
   uint8_t random[SSL3_RANDOM_SIZE];
 
   /* Determine the length of the V2ClientHello. */
-  assert(s->packet_length >= SSL3_RT_HEADER_LENGTH);
-  p = (const uint8_t *)s->packet;
+  assert(ssl_read_buffer_len(s) >= SSL3_RT_HEADER_LENGTH);
+  p = ssl_read_buffer(s);
   msg_length = ((p[0] & 0x7f) << 8) | p[1];
   if (msg_length > (1024 * 4)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_RECORD_TOO_LARGE);
@@ -668,16 +660,13 @@
     return -1;
   }
 
-  /* Read the remainder of the V2ClientHello. We have previously read
-   * |SSL3_RT_HEADER_LENGTH| bytes in ssl3_get_initial_bytes. */
-  ret = ssl3_read_n(s, msg_length - (SSL3_RT_HEADER_LENGTH - 2),
-                    1 /* extend */);
+  /* Read the remainder of the V2ClientHello. */
+  ret = ssl_read_buffer_extend_to(s, 2 + msg_length);
   if (ret <= 0) {
     return ret;
   }
-  assert(s->packet_length == msg_length + 2);
-  CBS_init(&v2_client_hello, (const uint8_t *)s->packet + 2,
-           msg_length);
+  assert(ssl_read_buffer_len(s) == msg_length + 2);
+  CBS_init(&v2_client_hello, ssl_read_buffer(s) + 2, msg_length);
 
   /* The V2ClientHello without the length is incorporated into the handshake
    * hash. */
@@ -766,10 +755,9 @@
   /* The handshake message header is 4 bytes. */
   s->s3->tmp.message_size = len - 4;
 
-  /* The V2ClientHello was processed, so it may be released now. */
-  if (s->s3->rbuf.left == 0) {
-    ssl3_release_read_buffer(s);
-  }
+  /* Consume and discard the V2ClientHello. */
+  ssl_read_buffer_consume(s, 2 + msg_length);
+  ssl_read_buffer_discard(s);
 
   return 1;
 }
diff --git a/ssl/ssl_buffer.c b/ssl/ssl_buffer.c
new file mode 100644
index 0000000..37f27be
--- /dev/null
+++ b/ssl/ssl_buffer.c
@@ -0,0 +1,309 @@
+/* Copyright (c) 2015, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include <openssl/ssl.h>
+
+#include <assert.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include <openssl/mem.h>
+#include <openssl/type_check.h>
+
+#include "internal.h"
+
+
+OPENSSL_COMPILE_ASSERT(0xffff <= INT_MAX, uint16_fits_in_int);
+
+OPENSSL_COMPILE_ASSERT((SSL3_ALIGN_PAYLOAD & (SSL3_ALIGN_PAYLOAD - 1)) == 0,
+                       align_to_a_power_of_two);
+
+/* setup_buffer initializes |buf| with capacity |cap|, aligned such that data
+ * written after |header_len| is aligned to a |SSL3_ALIGN_PAYLOAD|-byte
+ * boundary. It returns one on success and zero on error. */
+static int setup_buffer(SSL3_BUFFER *buf, size_t header_len, size_t cap) {
+  if (buf->buf != NULL || cap > 0xffff) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+
+  /* Add up to |SSL3_ALIGN_PAYLOAD| - 1 bytes of slack for alignment. */
+  buf->buf = OPENSSL_malloc(cap + SSL3_ALIGN_PAYLOAD - 1);
+  if (buf->buf == NULL) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+
+  /* Arrange the buffer such that the record body is aligned. */
+  buf->offset = (0 - header_len - (uintptr_t)buf->buf) &
+                (SSL3_ALIGN_PAYLOAD - 1);
+  buf->len = 0;
+  buf->cap = cap;
+  return 1;
+}
+
+static void consume_buffer(SSL3_BUFFER *buf, size_t len) {
+  if (len > buf->len) {
+    abort();
+  }
+  buf->offset += (uint16_t)len;
+  buf->len -= (uint16_t)len;
+  buf->cap -= (uint16_t)len;
+}
+
+static void clear_buffer(SSL3_BUFFER *buf) {
+  OPENSSL_free(buf->buf);
+  memset(buf, 0, sizeof(SSL3_BUFFER));
+}
+
+OPENSSL_COMPILE_ASSERT(DTLS1_RT_HEADER_LENGTH + SSL3_RT_MAX_ENCRYPTED_LENGTH +
+                           SSL3_RT_MAX_EXTRA <= 0xffff,
+                       maximum_read_buffer_too_large);
+
+/* setup_read_buffer initializes the read buffer if not already initialized. It
+ * returns one on success and zero on failure. */
+static int setup_read_buffer(SSL *ssl) {
+  SSL3_BUFFER *buf = &ssl->s3->read_buffer;
+
+  if (buf->buf != NULL) {
+    return 1;
+  }
+
+  size_t header_len = ssl_record_prefix_len(ssl);
+  size_t cap = SSL3_RT_HEADER_LENGTH + SSL3_RT_MAX_PLAIN_LENGTH;
+  if (ssl->options & SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER) {
+    cap += SSL3_RT_MAX_EXTRA;
+  }
+
+  return setup_buffer(buf, header_len, cap);
+}
+
+uint8_t *ssl_read_buffer(SSL *ssl) {
+  return ssl->s3->read_buffer.buf + ssl->s3->read_buffer.offset;
+}
+
+size_t ssl_read_buffer_len(const SSL *ssl) {
+  return ssl->s3->read_buffer.len;
+}
+
+static int dtls_read_buffer_next_packet(SSL *ssl) {
+  SSL3_BUFFER *buf = &ssl->s3->read_buffer;
+
+  if (buf->len > 0) {
+    /* It is an error to call |dtls_read_buffer_extend| when the read buffer is
+     * not empty. */
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return -1;
+  }
+
+  /* Read a single packet from |ssl->rbio|. |buf->cap| must fit in an int. */
+  ssl->rwstate = SSL_READING;
+  int ret = BIO_read(ssl->rbio, buf->buf + buf->offset, (int)buf->cap);
+  if (ret <= 0) {
+    return ret;
+  }
+  ssl->rwstate = SSL_NOTHING;
+  /* |BIO_read| was bound by |buf->cap|, so this cannot overflow. */
+  buf->len = (uint16_t)ret;
+  return 1;
+}
+
+static int tls_read_buffer_extend_to(SSL *ssl, size_t len) {
+  SSL3_BUFFER *buf = &ssl->s3->read_buffer;
+
+  if (len > buf->cap) {
+    /* This may occur if |SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER| was toggled after
+     * |setup_read_buffer| was called. Stay within bounds, but do not attempt to
+     * recover. */
+    OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);
+    return -1;
+  }
+
+  /* Read until the target length is reached. */
+  while (buf->len < len) {
+    /* The amount of data to read is bounded by |buf->cap|, which must fit in an
+     * int. */
+    ssl->rwstate = SSL_READING;
+    int ret = BIO_read(ssl->rbio, buf->buf + buf->offset + buf->len,
+                       (int)(len - buf->len));
+    if (ret <= 0) {
+      return ret;
+    }
+    ssl->rwstate = SSL_NOTHING;
+    /* |BIO_read| was bound by |buf->cap - buf->len|, so this cannot
+     * overflow. */
+    buf->len += (uint16_t)ret;
+  }
+
+  return 1;
+}
+
+int ssl_read_buffer_extend_to(SSL *ssl, size_t len) {
+  /* |ssl_read_buffer_extend_to| implicitly discards any consumed data. */
+  ssl_read_buffer_discard(ssl);
+
+  if (!setup_read_buffer(ssl)) {
+    return -1;
+  }
+
+  if (ssl->rbio == NULL) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_BIO_NOT_SET);
+    return -1;
+  }
+
+  ERR_clear_system_error();
+
+  int ret;
+  if (SSL_IS_DTLS(ssl)) {
+    /* |len| is ignored for a datagram transport. */
+    ret = dtls_read_buffer_next_packet(ssl);
+  } else {
+    ret = tls_read_buffer_extend_to(ssl, len);
+  }
+
+  if (ret <= 0) {
+    /* If the buffer was empty originally and remained empty after attempting to
+     * extend it, release the buffer until the next attempt. */
+    ssl_read_buffer_discard(ssl);
+  }
+  return ret;
+}
+
+void ssl_read_buffer_consume(SSL *ssl, size_t len) {
+  SSL3_BUFFER *buf = &ssl->s3->read_buffer;
+
+  consume_buffer(buf, len);
+  if (!SSL_IS_DTLS(ssl)) {
+    /* The TLS stack never reads beyond the current record, so there will never
+     * be unconsumed data. If read-ahead is ever reimplemented,
+     * |ssl_read_buffer_discard| will require a |memcpy| to shift the excess
+     * back to the front of the buffer, to ensure there is enough space for the
+     * next record. */
+     assert(buf->len == 0);
+  }
+}
+
+void ssl_read_buffer_discard(SSL *ssl) {
+  if (ssl->s3->read_buffer.len == 0) {
+    ssl_read_buffer_clear(ssl);
+  }
+}
+
+void ssl_read_buffer_clear(SSL *ssl) {
+  clear_buffer(&ssl->s3->read_buffer);
+}
+
+
+int ssl_write_buffer_is_pending(const SSL *ssl) {
+  return ssl->s3->write_buffer.len > 0;
+}
+
+OPENSSL_COMPILE_ASSERT(SSL3_RT_HEADER_LENGTH * 2 +
+                           SSL3_RT_SEND_MAX_ENCRYPTED_OVERHEAD * 2 +
+                           SSL3_RT_MAX_PLAIN_LENGTH <= 0xffff,
+                       maximum_tls_write_buffer_too_large);
+
+OPENSSL_COMPILE_ASSERT(DTLS1_RT_HEADER_LENGTH +
+                           SSL3_RT_SEND_MAX_ENCRYPTED_OVERHEAD +
+                           SSL3_RT_MAX_PLAIN_LENGTH <= 0xffff,
+                       maximum_dtls_write_buffer_too_large);
+
+int ssl_write_buffer_init(SSL *ssl, uint8_t **out_ptr, size_t max_len) {
+  SSL3_BUFFER *buf = &ssl->s3->write_buffer;
+
+  if (buf->buf != NULL) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+
+  size_t header_len = ssl_seal_prefix_len(ssl);
+
+  /* TODO(davidben): This matches the original behavior in keeping the malloc
+   * size consistent. Does this matter? |cap| could just be |max_len|. */
+  size_t cap = SSL3_RT_HEADER_LENGTH + SSL3_RT_MAX_PLAIN_LENGTH +
+               SSL3_RT_SEND_MAX_ENCRYPTED_OVERHEAD;
+  if (!SSL_IS_DTLS(ssl) && (ssl->mode & SSL_MODE_CBC_RECORD_SPLITTING)) {
+    cap += SSL3_RT_HEADER_LENGTH + SSL3_RT_SEND_MAX_ENCRYPTED_OVERHEAD;
+  }
+
+  if (max_len > cap) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);
+    return 0;
+  }
+
+  if (!setup_buffer(buf, header_len, cap)) {
+    return 0;
+  }
+  *out_ptr = buf->buf + buf->offset;
+  return 1;
+}
+
+void ssl_write_buffer_set_len(SSL *ssl, size_t len) {
+  SSL3_BUFFER *buf = &ssl->s3->write_buffer;
+
+  if (len > buf->cap) {
+    abort();
+  }
+  buf->len = len;
+}
+
+static int tls_write_buffer_flush(SSL *ssl) {
+  SSL3_BUFFER *buf = &ssl->s3->write_buffer;
+
+  while (buf->len > 0) {
+    ssl->rwstate = SSL_WRITING;
+    int ret = BIO_write(ssl->wbio, buf->buf + buf->offset, buf->len);
+    if (ret <= 0) {
+      return ret;
+    }
+    ssl->rwstate = SSL_NOTHING;
+    consume_buffer(buf, (size_t)ret);
+  }
+  ssl_write_buffer_clear(ssl);
+  return 1;
+}
+
+static int dtls_write_buffer_flush(SSL *ssl) {
+  SSL3_BUFFER *buf = &ssl->s3->write_buffer;
+  if (buf->len == 0) {
+    return 1;
+  }
+
+  int ret = BIO_write(ssl->wbio, buf->buf + buf->offset, buf->len);
+  /* Drop the write buffer whether or not the write succeeded synchronously.
+   * TODO(davidben): How does this interact with the retry flag? */
+  ssl_write_buffer_clear(ssl);
+  return (ret <= 0) ? ret : 1;
+}
+
+int ssl_write_buffer_flush(SSL *ssl) {
+  if (ssl->wbio == NULL) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_BIO_NOT_SET);
+    return -1;
+  }
+  ERR_clear_system_error();
+
+  if (SSL_IS_DTLS(ssl)) {
+    return dtls_write_buffer_flush(ssl);
+  } else {
+    return tls_write_buffer_flush(ssl);
+  }
+}
+
+void ssl_write_buffer_clear(SSL *ssl) {
+  clear_buffer(&ssl->s3->write_buffer);
+}
diff --git a/ssl/ssl_cipher.c b/ssl/ssl_cipher.c
index 551cc52..976c602 100644
--- a/ssl/ssl_cipher.c
+++ b/ssl/ssl_cipher.c
@@ -1677,3 +1677,34 @@
   /* It is optional in all others. */
   return 0;
 }
+
+size_t ssl_cipher_get_record_split_len(const SSL_CIPHER *cipher) {
+  size_t block_size;
+  switch (cipher->algorithm_enc) {
+    case SSL_3DES:
+      block_size = 8;
+      break;
+    case SSL_AES128:
+    case SSL_AES256:
+      block_size = 16;
+      break;
+    default:
+      return 0;
+  }
+
+  size_t mac_len;
+  switch (cipher->algorithm_mac) {
+    case SSL_MD5:
+      mac_len = MD5_DIGEST_LENGTH;
+      break;
+    case SSL_SHA1:
+      mac_len = SHA_DIGEST_LENGTH;
+      break;
+    default:
+      return 0;
+  }
+
+  size_t ret = 1 + mac_len;
+  ret += block_size - (ret % block_size);
+  return ret;
+}
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 5e322d9..2964c37 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -207,14 +207,10 @@
    * |ssl3_new|. */
 
   ssl->rwstate = SSL_NOTHING;
-  ssl->rstate = SSL_ST_READ_HEADER;
 
   BUF_MEM_free(ssl->init_buf);
   ssl->init_buf = NULL;
 
-  ssl->packet = NULL;
-  ssl->packet_length = 0;
-
   ssl_clear_cipher_ctx(ssl);
 
   OPENSSL_free(ssl->next_proto_negotiated);
@@ -329,7 +325,6 @@
   assert(s->enc_method != NULL);
 
   s->rwstate = SSL_NOTHING;
-  s->rstate = SSL_ST_READ_HEADER;
 
   CRYPTO_new_ex_data(&g_ex_data_class_ssl, s, &s->ex_data);
 
@@ -754,10 +749,6 @@
 void SSL_set_read_ahead(SSL *s, int yes) { }
 
 int SSL_pending(const SSL *s) {
-  if (s->rstate == SSL_ST_READ_BODY) {
-    return 0;
-  }
-
   return (s->s3->rrec.type == SSL3_RT_APPLICATION_DATA) ? s->s3->rrec.length
                                                         : 0;
 }
diff --git a/ssl/ssl_stat.c b/ssl/ssl_stat.c
index 065e0a7..d5feb5d 100644
--- a/ssl/ssl_stat.c
+++ b/ssl/ssl_stat.c
@@ -351,30 +351,6 @@
   return str;
 }
 
-const char *SSL_rstate_string_long(const SSL *s) {
-  const char *str;
-
-  switch (s->rstate) {
-    case SSL_ST_READ_HEADER:
-      str = "read header";
-      break;
-
-    case SSL_ST_READ_BODY:
-      str = "read body";
-      break;
-
-    case SSL_ST_READ_DONE:
-      str = "read done";
-      break;
-
-    default:
-      str = "unknown";
-      break;
-  }
-
-  return str;
-}
-
 const char *SSL_state_string(const SSL *s) {
   const char *str;
 
@@ -906,27 +882,3 @@
 
   return str;
 }
-
-const char *SSL_rstate_string(const SSL *s) {
-  const char *str;
-
-  switch (s->rstate) {
-    case SSL_ST_READ_HEADER:
-      str = "RH";
-      break;
-
-    case SSL_ST_READ_BODY:
-      str = "RB";
-      break;
-
-    case SSL_ST_READ_DONE:
-      str = "RD";
-      break;
-
-    default:
-      str = "unknown";
-      break;
-  }
-
-  return str;
-}
diff --git a/ssl/tls_record.c b/ssl/tls_record.c
new file mode 100644
index 0000000..ae75495
--- /dev/null
+++ b/ssl/tls_record.c
@@ -0,0 +1,318 @@
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+ * All rights reserved.
+ *
+ * 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:
+ * 1. Redistributions of source code must retain the copyright
+ *    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 the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *    "This product includes cryptographic software written by
+ *     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
+ *    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
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * 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
+ * [including the GNU Public Licence.]
+ */
+/* ====================================================================
+ * Copyright (c) 1998-2002 The OpenSSL Project.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    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
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. All advertising materials mentioning features or use of this
+ *    software must display the following acknowledgment:
+ *    "This product includes software developed by the OpenSSL Project
+ *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+ *
+ * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please contact
+ *    openssl-core@openssl.org.
+ *
+ * 5. Products derived from this software may not be called "OpenSSL"
+ *    nor may "OpenSSL" appear in their names without prior written
+ *    permission of the OpenSSL Project.
+ *
+ * 6. Redistributions of any form whatsoever must retain the following
+ *    acknowledgment:
+ *    "This product includes software developed by the OpenSSL Project
+ *    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+ * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT 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.
+ * ====================================================================
+ *
+ * This product includes cryptographic software written by Eric Young
+ * (eay@cryptsoft.com).  This product includes software written by Tim
+ * Hudson (tjh@cryptsoft.com). */
+
+#include <openssl/ssl.h>
+
+#include <assert.h>
+
+#include <openssl/bytestring.h>
+#include <openssl/err.h>
+
+#include "internal.h"
+
+
+size_t ssl_record_prefix_len(const SSL *ssl) {
+  if (SSL_IS_DTLS(ssl)) {
+    return DTLS1_RT_HEADER_LENGTH +
+           SSL_AEAD_CTX_explicit_nonce_len(ssl->aead_read_ctx);
+  } else {
+    return SSL3_RT_HEADER_LENGTH +
+           SSL_AEAD_CTX_explicit_nonce_len(ssl->aead_read_ctx);
+  }
+}
+
+size_t ssl_seal_prefix_len(const SSL *ssl) {
+  if (SSL_IS_DTLS(ssl)) {
+    return DTLS1_RT_HEADER_LENGTH +
+           SSL_AEAD_CTX_explicit_nonce_len(ssl->aead_write_ctx);
+  } else {
+    size_t ret = SSL3_RT_HEADER_LENGTH +
+                 SSL_AEAD_CTX_explicit_nonce_len(ssl->aead_write_ctx);
+    if (ssl->s3->need_record_splitting) {
+      ret += SSL3_RT_HEADER_LENGTH;
+      ret += ssl_cipher_get_record_split_len(ssl->aead_write_ctx->cipher);
+    }
+    return ret;
+  }
+}
+
+size_t ssl_max_seal_overhead(const SSL *ssl) {
+  if (SSL_IS_DTLS(ssl)) {
+    return DTLS1_RT_HEADER_LENGTH +
+           SSL_AEAD_CTX_max_overhead(ssl->aead_write_ctx);
+  } else {
+    size_t ret = SSL3_RT_HEADER_LENGTH +
+                 SSL_AEAD_CTX_max_overhead(ssl->aead_write_ctx);
+    if (ssl->s3->need_record_splitting) {
+      ret *= 2;
+    }
+    return ret;
+  }
+}
+
+enum ssl_open_record_t tls_open_record(
+    SSL *ssl, uint8_t *out_type, uint8_t *out, size_t *out_len,
+    size_t *out_consumed, uint8_t *out_alert, size_t max_out, const uint8_t *in,
+    size_t in_len) {
+  CBS cbs;
+  CBS_init(&cbs, in, in_len);
+
+  /* Decode the record header. */
+  uint8_t type;
+  uint16_t version, ciphertext_len;
+  if (!CBS_get_u8(&cbs, &type) ||
+      !CBS_get_u16(&cbs, &version) ||
+      !CBS_get_u16(&cbs, &ciphertext_len)) {
+    *out_consumed = SSL3_RT_HEADER_LENGTH;
+    return ssl_open_record_partial;
+  }
+
+  /* Check the version. */
+  if ((ssl->s3->have_version && version != ssl->version) ||
+      (version >> 8) != SSL3_VERSION_MAJOR) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_VERSION_NUMBER);
+    *out_alert = SSL_AD_PROTOCOL_VERSION;
+    return ssl_open_record_error;
+  }
+
+  /* Check the ciphertext length. */
+  size_t extra = 0;
+  if (ssl->options & SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER) {
+    extra = SSL3_RT_MAX_EXTRA;
+  }
+  if (ciphertext_len > SSL3_RT_MAX_ENCRYPTED_LENGTH + extra) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_ENCRYPTED_LENGTH_TOO_LONG);
+    *out_alert = SSL_AD_RECORD_OVERFLOW;
+    return ssl_open_record_error;
+  }
+
+  /* Extract the body. */
+  CBS body;
+  if (!CBS_get_bytes(&cbs, &body, ciphertext_len)) {
+    *out_consumed = SSL3_RT_HEADER_LENGTH + (size_t)ciphertext_len;
+    return ssl_open_record_partial;
+  }
+
+  if (ssl->msg_callback != NULL) {
+    ssl->msg_callback(0 /* read */, 0, SSL3_RT_HEADER, in,
+                      SSL3_RT_HEADER_LENGTH, ssl, ssl->msg_callback_arg);
+  }
+
+  /* Decrypt the body. */
+  size_t plaintext_len;
+  if (!SSL_AEAD_CTX_open(ssl->aead_read_ctx, out, &plaintext_len, max_out,
+                         type, version, ssl->s3->read_sequence, CBS_data(&body),
+                         CBS_len(&body))) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC);
+    *out_alert = SSL_AD_BAD_RECORD_MAC;
+    return ssl_open_record_error;
+  }
+  if (!ssl3_record_sequence_update(ssl->s3->read_sequence, 8)) {
+    *out_alert = SSL_AD_INTERNAL_ERROR;
+    return ssl_open_record_error;
+  }
+
+  /* Check the plaintext length. */
+  if (plaintext_len > SSL3_RT_MAX_PLAIN_LENGTH + extra) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DATA_LENGTH_TOO_LONG);
+    *out_alert = SSL_AD_RECORD_OVERFLOW;
+    return ssl_open_record_error;
+  }
+
+  *out_type = type;
+  *out_len = plaintext_len;
+  *out_consumed = in_len - CBS_len(&cbs);
+  return ssl_open_record_success;
+}
+
+static int do_seal_record(SSL *ssl, uint8_t *out, size_t *out_len,
+                          size_t max_out, uint8_t type, const uint8_t *in,
+                          size_t in_len) {
+  if (max_out < SSL3_RT_HEADER_LENGTH) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);
+    return 0;
+  }
+  /* Check the record header does not alias any part of the input.
+   * |SSL_AEAD_CTX_seal| will internally enforce other aliasing requirements. */
+  if (in < out + SSL3_RT_HEADER_LENGTH && out < in + in_len) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_OUTPUT_ALIASES_INPUT);
+    return 0;
+  }
+
+  out[0] = type;
+
+  /* Some servers hang if initial ClientHello is larger than 256 bytes and
+   * record version number > TLS 1.0. */
+  uint16_t wire_version = ssl->version;
+  if (!ssl->s3->have_version && ssl->version > SSL3_VERSION) {
+    wire_version = TLS1_VERSION;
+  }
+  out[1] = wire_version >> 8;
+  out[2] = wire_version & 0xff;
+
+  size_t ciphertext_len;
+  if (!SSL_AEAD_CTX_seal(ssl->aead_write_ctx, out + SSL3_RT_HEADER_LENGTH,
+                         &ciphertext_len, max_out - SSL3_RT_HEADER_LENGTH,
+                         type, wire_version, ssl->s3->write_sequence, in,
+                         in_len) ||
+      !ssl3_record_sequence_update(ssl->s3->write_sequence, 8)) {
+    return 0;
+  }
+
+  if (ciphertext_len >= 1 << 16) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
+    return 0;
+  }
+  out[3] = ciphertext_len >> 8;
+  out[4] = ciphertext_len & 0xff;
+
+  *out_len = SSL3_RT_HEADER_LENGTH + ciphertext_len;
+
+  if (ssl->msg_callback) {
+    ssl->msg_callback(1 /* write */, 0, SSL3_RT_HEADER, out,
+                      SSL3_RT_HEADER_LENGTH, ssl, ssl->msg_callback_arg);
+  }
+
+  return 1;
+}
+
+int tls_seal_record(SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
+                    uint8_t type, const uint8_t *in, size_t in_len) {
+  size_t frag_len = 0;
+  if (ssl->s3->need_record_splitting && type == SSL3_RT_APPLICATION_DATA &&
+      in_len > 1) {
+    /* |do_seal_record| will notice if it clobbers |in[0]|, but not if it
+     * aliases the rest of |in|. */
+    if (in + 1 <= out && out < in + in_len) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_OUTPUT_ALIASES_INPUT);
+      return 0;
+    }
+    /* Ensure |do_seal_record| does not write beyond |in[0]|. */
+    size_t frag_max_out = max_out;
+    if (out <= in + 1 && (in + 1) - out < frag_max_out) {
+      frag_max_out = (in + 1) - out;
+    }
+    if (!do_seal_record(ssl, out, &frag_len, frag_max_out, type, in, 1)) {
+      return 0;
+    }
+    in++;
+    in_len--;
+    out += frag_len;
+    max_out -= frag_len;
+
+    assert(SSL3_RT_HEADER_LENGTH +
+               ssl_cipher_get_record_split_len(ssl->aead_write_ctx->cipher) ==
+           frag_len);
+  }
+
+  if (!do_seal_record(ssl, out, out_len, max_out, type, in, in_len)) {
+    return 0;
+  }
+  *out_len += frag_len;
+  return 1;
+}