Don't use the buffer BIO in DTLS.

Instead, "writing" a message merely adds it to the outgoing_messages
structure. The code to write the flight then loops over it all and now
shares code with retransmission. The verbs here are all a little odd,
but they'll be fixed in later commits.

In doing so, this fixes a slight miscalculation of the record-layer
overhead when retransmitting a flight that spans two epochs. (We'd use
the encrypted epoch's overhead for the unencrypted epoch.)

BUG=72

Change-Id: I8ac897c955cc74799f8b5ca6923906e97d6dad17
Reviewed-on: https://boringssl-review.googlesource.com/13223
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 1491a53..df227fb 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -4126,10 +4126,6 @@
   /* init_num is the length of the current handshake message body. */
   uint32_t init_num;
 
-  /* init_off, in DTLS, is the number of bytes of the current message that have
-   * been written. */
-  uint32_t init_off;
-
   struct ssl3_state_st *s3;  /* SSLv3 variables */
   struct dtls1_state_st *d1; /* DTLSv1 variables */
 
diff --git a/ssl/d1_both.c b/ssl/d1_both.c
index d3e4a92..1d89636 100644
--- a/ssl/d1_both.c
+++ b/ssl/d1_both.c
@@ -122,6 +122,7 @@
 #include <openssl/evp.h>
 #include <openssl/mem.h>
 #include <openssl/rand.h>
+#include <openssl/type_check.h>
 #include <openssl/x509.h>
 
 #include "../crypto/internal.h"
@@ -311,9 +312,9 @@
   }
 
   /* Cross-epoch records are discarded, but we may receive out-of-order
-   * application data between ChangeCipherSpec and Finished or a ChangeCipherSpec
-   * before the appropriate point in the handshake. Those must be silently
-   * discarded.
+   * application data between ChangeCipherSpec and Finished or a
+   * ChangeCipherSpec before the appropriate point in the handshake. Those must
+   * be silently discarded.
    *
    * However, only allow the out-of-order records in the correct epoch.
    * Application data must come in the encrypted epoch, and ChangeCipherSpec in
@@ -384,8 +385,8 @@
     assert(msg_len > 0);
 
     /* Copy the body into the fragment. */
-    OPENSSL_memcpy(frag->data + DTLS1_HM_HEADER_LENGTH + frag_off, CBS_data(&body),
-           CBS_len(&body));
+    OPENSSL_memcpy(frag->data + DTLS1_HM_HEADER_LENGTH + frag_off,
+                   CBS_data(&body), CBS_len(&body));
     dtls1_hm_fragment_mark(frag, frag_off, frag_off + frag_len);
   }
 
@@ -507,219 +508,14 @@
 
 /* Sending handshake messages. */
 
-static void dtls1_update_mtu(SSL *ssl) {
-  /* TODO(davidben): What is this code doing and do we need it? */
-  if (ssl->d1->mtu < dtls1_min_mtu() &&
-      !(SSL_get_options(ssl) & SSL_OP_NO_QUERY_MTU)) {
-    long mtu = BIO_ctrl(ssl->wbio, BIO_CTRL_DGRAM_QUERY_MTU, 0, NULL);
-    if (mtu >= 0 && mtu <= (1 << 30) && (unsigned)mtu >= dtls1_min_mtu()) {
-      ssl->d1->mtu = (unsigned)mtu;
-    } else {
-      ssl->d1->mtu = kDefaultMTU;
-      BIO_ctrl(ssl->wbio, BIO_CTRL_DGRAM_SET_MTU, ssl->d1->mtu, NULL);
-    }
-  }
-
-  /* The MTU should be above the minimum now. */
-  assert(ssl->d1->mtu >= dtls1_min_mtu());
-}
-
-/* dtls1_max_record_size returns the maximum record body length that may be
- * written without exceeding the MTU. It accounts for any buffering installed on
- * the write BIO. If no record may be written, it returns zero. */
-static size_t dtls1_max_record_size(const SSL *ssl) {
-  size_t ret = ssl->d1->mtu;
-
-  size_t overhead = SSL_max_seal_overhead(ssl);
-  if (ret <= overhead) {
-    return 0;
-  }
-  ret -= overhead;
-
-  size_t pending = BIO_wpending(ssl->wbio);
-  if (ret <= pending) {
-    return 0;
-  }
-  ret -= pending;
-
-  return ret;
-}
-
-static int dtls1_write_change_cipher_spec(SSL *ssl,
-                                          enum dtls1_use_epoch_t use_epoch) {
-  dtls1_update_mtu(ssl);
-
-  /* During the handshake, wbio is buffered to pack messages together. Flush the
-   * buffer if the ChangeCipherSpec would not fit in a packet. */
-  if (dtls1_max_record_size(ssl) == 0) {
-    int ret = BIO_flush(ssl->wbio);
-    if (ret <= 0) {
-      ssl->rwstate = SSL_WRITING;
-      return ret;
-    }
-  }
-
-  static const uint8_t kChangeCipherSpec[1] = {SSL3_MT_CCS};
-  int ret =
-      dtls1_write_record(ssl, SSL3_RT_CHANGE_CIPHER_SPEC, kChangeCipherSpec,
-                         sizeof(kChangeCipherSpec), use_epoch);
-  if (ret <= 0) {
-    return ret;
-  }
-
-  ssl_do_msg_callback(ssl, 1 /* write */, SSL3_RT_CHANGE_CIPHER_SPEC,
-                      kChangeCipherSpec, sizeof(kChangeCipherSpec));
-  return 1;
-}
-
-/* dtls1_do_handshake_write writes handshake message |in| using the given epoch,
- * starting |offset| bytes into the message body. It returns one on success. On
- * error, it returns <= 0 and sets |*out_offset| to the number of bytes of body
- * that were successfully written. This may be used to retry the write
- * later. |in| must be a reassembled handshake message with the full DTLS
- * handshake header. */
-static int dtls1_do_handshake_write(SSL *ssl, size_t *out_offset,
-                                    const uint8_t *in, size_t offset,
-                                    size_t len,
-                                    enum dtls1_use_epoch_t use_epoch) {
-  dtls1_update_mtu(ssl);
-
-  int ret = -1;
-  CBB cbb;
-  CBB_zero(&cbb);
-  /* Allocate a temporary buffer to hold the message fragments to avoid
-   * clobbering the message. */
-  uint8_t *buf = OPENSSL_malloc(ssl->d1->mtu);
-  if (buf == NULL) {
-    goto err;
-  }
-
-  /* Although it may be sent as multiple fragments, a DTLS message must be sent
-   * serialized as a single fragment for purposes of |ssl_do_msg_callback| and
-   * the handshake hash. */
-  CBS cbs, body;
-  struct hm_header_st hdr;
-  CBS_init(&cbs, in, len);
-  if (!dtls1_parse_fragment(&cbs, &hdr, &body) ||
-      hdr.frag_off != 0 ||
-      hdr.frag_len != CBS_len(&body) ||
-      hdr.msg_len != CBS_len(&body) ||
-      !CBS_skip(&body, offset) ||
-      CBS_len(&cbs) != 0) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    goto err;
-  }
-
-  do {
-    /* During the handshake, wbio is buffered to pack messages together. Flush
-     * the buffer if there isn't enough room to make progress. */
-    if (dtls1_max_record_size(ssl) < DTLS1_HM_HEADER_LENGTH + 1) {
-      int flush_ret = BIO_flush(ssl->wbio);
-      if (flush_ret <= 0) {
-        ssl->rwstate = SSL_WRITING;
-        ret = flush_ret;
-        goto err;
-      }
-      assert(BIO_wpending(ssl->wbio) == 0);
-    }
-
-    size_t todo = dtls1_max_record_size(ssl);
-    if (todo < DTLS1_HM_HEADER_LENGTH + 1) {
-      /* To make forward progress, the MTU must, at minimum, fit the handshake
-       * header and one byte of handshake body. */
-      OPENSSL_PUT_ERROR(SSL, SSL_R_MTU_TOO_SMALL);
-      goto err;
-    }
-    todo -= DTLS1_HM_HEADER_LENGTH;
-
-    if (todo > CBS_len(&body)) {
-      todo = CBS_len(&body);
-    }
-    if (todo >= (1u << 24)) {
-      todo = (1u << 24) - 1;
-    }
-
-    size_t buf_len;
-    if (!CBB_init_fixed(&cbb, buf, ssl->d1->mtu) ||
-        !CBB_add_u8(&cbb, hdr.type) ||
-        !CBB_add_u24(&cbb, hdr.msg_len) ||
-        !CBB_add_u16(&cbb, hdr.seq) ||
-        !CBB_add_u24(&cbb, offset) ||
-        !CBB_add_u24(&cbb, todo) ||
-        !CBB_add_bytes(&cbb, CBS_data(&body), todo) ||
-        !CBB_finish(&cbb, NULL, &buf_len)) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      goto err;
-    }
-
-    int write_ret =
-        dtls1_write_record(ssl, SSL3_RT_HANDSHAKE, buf, buf_len, use_epoch);
-    if (write_ret <= 0) {
-      ret = write_ret;
-      goto err;
-    }
-
-    if (!CBS_skip(&body, todo)) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      goto err;
-    }
-    offset += todo;
-  } while (CBS_len(&body) != 0);
-
-  ssl_do_msg_callback(ssl, 1 /* write */, SSL3_RT_HANDSHAKE, in, len);
-
-  ret = 1;
-
-err:
-  *out_offset = offset;
-  CBB_cleanup(&cbb);
-  OPENSSL_free(buf);
-  return ret;
-}
-
 void dtls_clear_outgoing_messages(SSL *ssl) {
   for (size_t i = 0; i < ssl->d1->outgoing_messages_len; i++) {
     OPENSSL_free(ssl->d1->outgoing_messages[i].data);
     ssl->d1->outgoing_messages[i].data = NULL;
   }
   ssl->d1->outgoing_messages_len = 0;
-}
-
-/* dtls1_add_change_cipher_spec adds a ChangeCipherSpec to the current
- * handshake flight. */
-static int dtls1_add_change_cipher_spec(SSL *ssl) {
-  if (ssl->d1->outgoing_messages_len >= SSL_MAX_HANDSHAKE_FLIGHT) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return 0;
-  }
-
-  DTLS_OUTGOING_MESSAGE *msg =
-      &ssl->d1->outgoing_messages[ssl->d1->outgoing_messages_len];
-  msg->data = NULL;
-  msg->len = 0;
-  msg->epoch = ssl->d1->w_epoch;
-  msg->is_ccs = 1;
-
-  ssl->d1->outgoing_messages_len++;
-  return 1;
-}
-
-static int dtls1_add_message(SSL *ssl, uint8_t *data, size_t len) {
-  if (ssl->d1->outgoing_messages_len >= SSL_MAX_HANDSHAKE_FLIGHT) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    OPENSSL_free(data);
-    return 0;
-  }
-
-  DTLS_OUTGOING_MESSAGE *msg =
-      &ssl->d1->outgoing_messages[ssl->d1->outgoing_messages_len];
-  msg->data = data;
-  msg->len = len;
-  msg->epoch = ssl->d1->w_epoch;
-  msg->is_ccs = 0;
-
-  ssl->d1->outgoing_messages_len++;
-  return 1;
+  ssl->d1->outgoing_written = 0;
+  ssl->d1->outgoing_offset = 0;
 }
 
 int dtls1_init_message(SSL *ssl, CBB *cbb, CBB *body, uint8_t type) {
@@ -752,36 +548,87 @@
   return 1;
 }
 
-int dtls1_queue_message(SSL *ssl, uint8_t *msg, size_t len) {
-  ssl3_update_handshake_hash(ssl, msg, len);
+/* add_outgoing adds a new handshake message or ChangeCipherSpec to the current
+ * outgoing flight. It returns one on success and zero on error. In both cases,
+ * it takes ownership of |data| and releases it with |OPENSSL_free| when
+ * done. */
+static int add_outgoing(SSL *ssl, int is_ccs, uint8_t *data, size_t len) {
+  OPENSSL_COMPILE_ASSERT(SSL_MAX_HANDSHAKE_FLIGHT <
+                             (1 << 8 * sizeof(ssl->d1->outgoing_messages_len)),
+                         outgoing_messages_len_is_too_small);
+  if (ssl->d1->outgoing_messages_len >= SSL_MAX_HANDSHAKE_FLIGHT) {
+    assert(0);
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    OPENSSL_free(data);
+    return 0;
+  }
 
-  ssl->d1->handshake_write_seq++;
-  ssl->init_off = 0;
-  return dtls1_add_message(ssl, msg, len);
+  if (!is_ccs) {
+    ssl3_update_handshake_hash(ssl, data, len);
+    ssl->d1->handshake_write_seq++;
+  }
+
+  DTLS_OUTGOING_MESSAGE *msg =
+      &ssl->d1->outgoing_messages[ssl->d1->outgoing_messages_len];
+  msg->data = data;
+  msg->len = len;
+  msg->epoch = ssl->d1->w_epoch;
+  msg->is_ccs = is_ccs;
+
+  ssl->d1->outgoing_messages_len++;
+  return 1;
+}
+
+int dtls1_queue_message(SSL *ssl, uint8_t *data, size_t len) {
+  return add_outgoing(ssl, 0 /* handshake */, data, len);
 }
 
 int dtls1_write_message(SSL *ssl) {
-  if (ssl->d1->outgoing_messages_len == 0) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
-  }
-
-  const DTLS_OUTGOING_MESSAGE *msg =
-      &ssl->d1->outgoing_messages[ssl->d1->outgoing_messages_len - 1];
-  if (msg->is_ccs) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return -1;
-  }
-
-  size_t offset = ssl->init_off;
-  int ret = dtls1_do_handshake_write(ssl, &offset, msg->data, offset, msg->len,
-                                     dtls1_use_current_epoch);
-  ssl->init_off = offset;
-  return ret;
+  /* The message is written in |dtls1_flush_flight|. */
+  return 1;
 }
 
-static int dtls1_retransmit_message(SSL *ssl,
-                                    const DTLS_OUTGOING_MESSAGE *msg) {
+int dtls1_send_change_cipher_spec(SSL *ssl) {
+  return add_outgoing(ssl, 1 /* ChangeCipherSpec */, NULL, 0);
+}
+
+/* dtls1_update_mtu updates the current MTU from the BIO, ensuring it is above
+ * the minimum. */
+static void dtls1_update_mtu(SSL *ssl) {
+  /* TODO(davidben): No consumer implements |BIO_CTRL_DGRAM_SET_MTU| and the
+   * only |BIO_CTRL_DGRAM_QUERY_MTU| implementation could use
+   * |SSL_set_mtu|. Does this need to be so complex?  */
+  if (ssl->d1->mtu < dtls1_min_mtu() &&
+      !(SSL_get_options(ssl) & SSL_OP_NO_QUERY_MTU)) {
+    long mtu = BIO_ctrl(ssl->wbio, BIO_CTRL_DGRAM_QUERY_MTU, 0, NULL);
+    if (mtu >= 0 && mtu <= (1 << 30) && (unsigned)mtu >= dtls1_min_mtu()) {
+      ssl->d1->mtu = (unsigned)mtu;
+    } else {
+      ssl->d1->mtu = kDefaultMTU;
+      BIO_ctrl(ssl->wbio, BIO_CTRL_DGRAM_SET_MTU, ssl->d1->mtu, NULL);
+    }
+  }
+
+  /* The MTU should be above the minimum now. */
+  assert(ssl->d1->mtu >= dtls1_min_mtu());
+}
+
+enum seal_result_t {
+  seal_error,
+  seal_no_progress,
+  seal_partial,
+  seal_success,
+};
+
+/* seal_next_message seals |msg|, which must be the next message, to |out|. If
+ * progress was made, it returns |seal_partial| or |seal_success| and sets
+ * |*out_len| to the number of bytes written. */
+static enum seal_result_t seal_next_message(SSL *ssl, uint8_t *out,
+                                            size_t *out_len, size_t max_out,
+                                            const DTLS_OUTGOING_MESSAGE *msg) {
+  assert(ssl->d1->outgoing_written < ssl->d1->outgoing_messages_len);
+  assert(msg == &ssl->d1->outgoing_messages[ssl->d1->outgoing_written]);
+
   /* DTLS renegotiation is unsupported, so only epochs 0 (NULL cipher) and 1
    * (negotiated cipher) exist. */
   assert(ssl->d1->w_epoch == 0 || ssl->d1->w_epoch == 1);
@@ -790,32 +637,156 @@
   if (ssl->d1->w_epoch == 1 && msg->epoch == 0) {
     use_epoch = dtls1_use_previous_epoch;
   }
+  size_t overhead = dtls_max_seal_overhead(ssl, use_epoch);
+  size_t prefix = dtls_seal_prefix_len(ssl, use_epoch);
 
-  /* TODO(davidben): This cannot handle non-blocking writes. */
-  int ret;
   if (msg->is_ccs) {
-    ret = dtls1_write_change_cipher_spec(ssl, use_epoch);
-  } else {
-    size_t offset = 0;
-    ret = dtls1_do_handshake_write(ssl, &offset, msg->data, offset, msg->len,
-                                   use_epoch);
+    /* Check there is room for the ChangeCipherSpec. */
+    static const uint8_t kChangeCipherSpec[1] = {SSL3_MT_CCS};
+    if (max_out < sizeof(kChangeCipherSpec) + overhead) {
+      return seal_no_progress;
+    }
+
+    if (!dtls_seal_record(ssl, out, out_len, max_out,
+                          SSL3_RT_CHANGE_CIPHER_SPEC, kChangeCipherSpec,
+                          sizeof(kChangeCipherSpec), use_epoch)) {
+      return seal_error;
+    }
+
+    ssl_do_msg_callback(ssl, 1 /* write */, SSL3_RT_CHANGE_CIPHER_SPEC,
+                        kChangeCipherSpec, sizeof(kChangeCipherSpec));
+    return seal_success;
   }
 
-  return ret;
+  /* DTLS messages are serialized as a single fragment in |msg|. */
+  CBS cbs, body;
+  struct hm_header_st hdr;
+  CBS_init(&cbs, msg->data, msg->len);
+  if (!dtls1_parse_fragment(&cbs, &hdr, &body) ||
+      hdr.frag_off != 0 ||
+      hdr.frag_len != CBS_len(&body) ||
+      hdr.msg_len != CBS_len(&body) ||
+      !CBS_skip(&body, ssl->d1->outgoing_offset) ||
+      CBS_len(&cbs) != 0) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return seal_error;
+  }
+
+  /* Determine how much progress can be made. */
+  if (max_out < DTLS1_HM_HEADER_LENGTH + 1 + overhead || max_out < prefix) {
+    return seal_no_progress;
+  }
+  size_t todo = CBS_len(&body);
+  if (todo > max_out - DTLS1_HM_HEADER_LENGTH - overhead) {
+    todo = max_out - DTLS1_HM_HEADER_LENGTH - overhead;
+  }
+
+  /* Assemble a fragment, to be sealed in-place. */
+  CBB cbb;
+  uint8_t *frag = out + prefix;
+  size_t max_frag = max_out - prefix, frag_len;
+  if (!CBB_init_fixed(&cbb, frag, max_frag) ||
+      !CBB_add_u8(&cbb, hdr.type) ||
+      !CBB_add_u24(&cbb, hdr.msg_len) ||
+      !CBB_add_u16(&cbb, hdr.seq) ||
+      !CBB_add_u24(&cbb, ssl->d1->outgoing_offset) ||
+      !CBB_add_u24(&cbb, todo) ||
+      !CBB_add_bytes(&cbb, CBS_data(&body), todo) ||
+      !CBB_finish(&cbb, NULL, &frag_len)) {
+    CBB_cleanup(&cbb);
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return seal_error;
+  }
+
+  ssl_do_msg_callback(ssl, 1 /* write */, SSL3_RT_HANDSHAKE, frag, frag_len);
+
+  if (!dtls_seal_record(ssl, out, out_len, max_out, SSL3_RT_HANDSHAKE,
+                        out + prefix, frag_len, use_epoch)) {
+    return seal_error;
+  }
+
+  if (todo == CBS_len(&body)) {
+    /* The next message is complete. */
+    ssl->d1->outgoing_offset = 0;
+    return seal_success;
+  }
+
+  ssl->d1->outgoing_offset += todo;
+  return seal_partial;
 }
 
-int dtls1_retransmit_outgoing_messages(SSL *ssl) {
-  /* Ensure we are packing handshake messages. */
-  const int was_buffered = ssl_is_wbio_buffered(ssl);
-  assert(was_buffered == SSL_in_init(ssl));
-  if (!was_buffered && !ssl_init_wbio_buffer(ssl)) {
-    return -1;
+/* seal_next_packet writes as much of the next flight as possible to |out| and
+ * advances |ssl->d1->outgoing_written| and |ssl->d1->outgoing_offset| as
+ * appropriate. */
+static int seal_next_packet(SSL *ssl, uint8_t *out, size_t *out_len,
+                            size_t max_out) {
+  int made_progress = 0;
+  size_t total = 0;
+  assert(ssl->d1->outgoing_written < ssl->d1->outgoing_messages_len);
+  for (; ssl->d1->outgoing_written < ssl->d1->outgoing_messages_len;
+       ssl->d1->outgoing_written++) {
+    const DTLS_OUTGOING_MESSAGE *msg =
+        &ssl->d1->outgoing_messages[ssl->d1->outgoing_written];
+    size_t len;
+    enum seal_result_t ret = seal_next_message(ssl, out, &len, max_out, msg);
+    switch (ret) {
+      case seal_error:
+        return 0;
+
+      case seal_no_progress:
+        goto packet_full;
+
+      case seal_partial:
+      case seal_success:
+        out += len;
+        max_out -= len;
+        total += len;
+        made_progress = 1;
+
+        if (ret == seal_partial) {
+          goto packet_full;
+        }
+        break;
+    }
   }
-  assert(ssl_is_wbio_buffered(ssl));
+
+packet_full:
+  /* The MTU was too small to make any progress. */
+  if (!made_progress) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_MTU_TOO_SMALL);
+    return 0;
+  }
+
+  *out_len = total;
+  return 1;
+}
+
+int dtls1_flush_flight(SSL *ssl) {
+  dtls1_update_mtu(ssl);
 
   int ret = -1;
-  for (size_t i = 0; i < ssl->d1->outgoing_messages_len; i++) {
-    if (dtls1_retransmit_message(ssl, &ssl->d1->outgoing_messages[i]) <= 0) {
+  uint8_t *packet = OPENSSL_malloc(ssl->d1->mtu);
+  if (packet == NULL) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
+  while (ssl->d1->outgoing_written < ssl->d1->outgoing_messages_len) {
+    uint8_t old_written = ssl->d1->outgoing_written;
+    uint32_t old_offset = ssl->d1->outgoing_offset;
+
+    size_t packet_len;
+    if (!seal_next_packet(ssl, packet, &packet_len, ssl->d1->mtu)) {
+      goto err;
+    }
+
+    int bio_ret = BIO_write(ssl->wbio, packet, packet_len);
+    if (bio_ret <= 0) {
+      /* Retry this packet the next time around. */
+      ssl->d1->outgoing_written = old_written;
+      ssl->d1->outgoing_offset = old_offset;
+      ssl->rwstate = SSL_WRITING;
+      ret = bio_ret;
       goto err;
     }
   }
@@ -823,23 +794,22 @@
   ret = BIO_flush(ssl->wbio);
   if (ret <= 0) {
     ssl->rwstate = SSL_WRITING;
-    goto err;
   }
 
 err:
-  if (!was_buffered) {
-    ssl_free_wbio_buffer(ssl);
-  }
+  OPENSSL_free(packet);
   return ret;
 }
 
-int dtls1_send_change_cipher_spec(SSL *ssl) {
-  int ret = dtls1_write_change_cipher_spec(ssl, dtls1_use_current_epoch);
-  if (ret <= 0) {
-    return ret;
-  }
-  dtls1_add_change_cipher_spec(ssl);
-  return 1;
+int dtls1_retransmit_outgoing_messages(SSL *ssl) {
+  /* Rewind to the start of the flight and write it again.
+   *
+   * TODO(davidben): This does not allow retransmits to be resumed on
+   * non-blocking write. */
+  ssl->d1->outgoing_written = 0;
+  ssl->d1->outgoing_offset = 0;
+
+  return dtls1_flush_flight(ssl);
 }
 
 unsigned int dtls1_min_mtu(void) {
diff --git a/ssl/dtls_method.c b/ssl/dtls_method.c
index 702b3c0..a774c82 100644
--- a/ssl/dtls_method.c
+++ b/ssl/dtls_method.c
@@ -99,14 +99,6 @@
   return cipher->algorithm_enc != SSL_eNULL;
 }
 
-static int dtls1_flush_flight(SSL *ssl) {
-  int ret = BIO_flush(ssl->wbio);
-  if (ret <= 0) {
-    ssl->rwstate = SSL_WRITING;
-  }
-  return ret;
-}
-
 static void dtls1_expect_flight(SSL *ssl) { dtls1_start_timer(ssl); }
 
 static void dtls1_received_flight(SSL *ssl) { dtls1_stop_timer(ssl); }
diff --git a/ssl/dtls_record.c b/ssl/dtls_record.c
index 35c08b0..879706d 100644
--- a/ssl/dtls_record.c
+++ b/ssl/dtls_record.c
@@ -249,16 +249,27 @@
   return ssl_open_record_success;
 }
 
-size_t dtls_seal_prefix_len(const SSL *ssl, enum dtls1_use_epoch_t use_epoch) {
-  const SSL_AEAD_CTX *aead = ssl->s3->aead_write_ctx;
+static const SSL_AEAD_CTX *get_write_aead(const SSL *ssl,
+                                          enum dtls1_use_epoch_t use_epoch) {
   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);
-    aead = NULL;
+    return NULL;
   }
 
-  return DTLS1_RT_HEADER_LENGTH + SSL_AEAD_CTX_explicit_nonce_len(aead);
+  return ssl->s3->aead_write_ctx;
+}
+
+size_t dtls_max_seal_overhead(const SSL *ssl,
+                              enum dtls1_use_epoch_t use_epoch) {
+  return DTLS1_RT_HEADER_LENGTH +
+         SSL_AEAD_CTX_max_overhead(get_write_aead(ssl, use_epoch));
+}
+
+size_t dtls_seal_prefix_len(const SSL *ssl, enum dtls1_use_epoch_t use_epoch) {
+  return DTLS1_RT_HEADER_LENGTH +
+         SSL_AEAD_CTX_explicit_nonce_len(get_write_aead(ssl, use_epoch));
 }
 
 int dtls_seal_record(SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
diff --git a/ssl/internal.h b/ssl/internal.h
index a320e72..ffe4d62 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -433,6 +433,10 @@
   dtls1_use_current_epoch,
 };
 
+/* dtls_max_seal_overhead returns the maximum overhead, in bytes, of sealing a
+ * record. */
+size_t dtls_max_seal_overhead(const SSL *ssl, enum dtls1_use_epoch_t use_epoch);
+
 /* dtls_seal_prefix_len returns the number of bytes of prefix to reserve in
  * front of the plaintext when sealing a record in-place. */
 size_t dtls_seal_prefix_len(const SSL *ssl, enum dtls1_use_epoch_t use_epoch);
@@ -1645,6 +1649,13 @@
   DTLS_OUTGOING_MESSAGE outgoing_messages[SSL_MAX_HANDSHAKE_FLIGHT];
   uint8_t outgoing_messages_len;
 
+  /* outgoing_written is the number of outgoing messages that have been
+   * written. */
+  uint8_t outgoing_written;
+  /* outgoing_offset is the number of bytes of the next outgoing message have
+   * been written. */
+  uint32_t outgoing_offset;
+
   unsigned int mtu; /* max DTLS packet size */
 
   /* num_timeouts is the number of times the retransmit timer has fired since
@@ -1779,6 +1790,8 @@
                          size_t *out_len);
 int dtls1_queue_message(SSL *ssl, uint8_t *msg, size_t len);
 int dtls1_write_message(SSL *ssl);
+int dtls1_send_change_cipher_spec(SSL *ssl);
+int dtls1_flush_flight(SSL *ssl);
 
 /* ssl_complete_message calls |finish_message| and |queue_message| on |cbb| to
  * queue the message for writing. */
@@ -1805,7 +1818,6 @@
 int dtls1_write_record(SSL *ssl, int type, const uint8_t *buf, size_t len,
                        enum dtls1_use_epoch_t use_epoch);
 
-int dtls1_send_change_cipher_spec(SSL *ssl);
 int dtls1_send_finished(SSL *ssl, int a, int b, const char *sender, int slen);
 int dtls1_retransmit_outgoing_messages(SSL *ssl);
 void dtls1_clear_record_buffer(SSL *ssl);
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index ba1ee8f..d653378 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -2028,6 +2028,13 @@
 }
 
 int ssl_init_wbio_buffer(SSL *ssl) {
+  if (SSL_is_dtls(ssl)) {
+    /* DTLS does not use the BIO buffer.
+     * TODO(davidben): Remove this altogether when TLS no longer uses it.
+     * https://crbug.com/boringssl/72. */
+    return 1;
+  }
+
   if (ssl->bbio != NULL) {
     /* Already buffered. */
     assert(ssl->bbio == ssl->wbio);
diff --git a/ssl/tls_record.c b/ssl/tls_record.c
index 362b0c2..6ff79c4 100644
--- a/ssl/tls_record.c
+++ b/ssl/tls_record.c
@@ -205,20 +205,19 @@
 }
 
 size_t SSL_max_seal_overhead(const SSL *ssl) {
-  size_t ret = SSL_AEAD_CTX_max_overhead(ssl->s3->aead_write_ctx);
   if (SSL_is_dtls(ssl)) {
-    ret += DTLS1_RT_HEADER_LENGTH;
-  } else if (ssl_uses_short_header(ssl, evp_aead_seal)) {
-    ret += 2;
-  } else {
-    ret += SSL3_RT_HEADER_LENGTH;
+    return dtls_max_seal_overhead(ssl, dtls1_use_current_epoch);
   }
+
+  size_t ret =
+      ssl_uses_short_header(ssl, evp_aead_seal) ? 2 : SSL3_RT_HEADER_LENGTH;
+  ret += SSL_AEAD_CTX_max_overhead(ssl->s3->aead_write_ctx);
   /* TLS 1.3 needs an extra byte for the encrypted record type. */
   if (ssl->s3->have_version &&
       ssl3_protocol_version(ssl) >= TLS1_3_VERSION) {
     ret += 1;
   }
-  if (!SSL_is_dtls(ssl) && ssl_needs_record_splitting(ssl)) {
+  if (ssl_needs_record_splitting(ssl)) {
     ret *= 2;
   }
   return ret;