From dff3e99458d0791da616a4c63541941bf9e171a8 Mon Sep 17 00:00:00 2001
From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
Date: Thu, 25 Oct 2018 14:22:55 +0900
Subject: [PATCH] Resend STREAM frame in the order of offset

---
 examples/client.cc           |   2 +-
 examples/server.cc           |   2 +-
 lib/CMakeLists.txt           |   1 +
 lib/Makefile.am              |   2 +
 lib/includes/ngtcp2/ngtcp2.h |  10 +-
 lib/ngtcp2_conn.c            | 705 +++++++++++++++++++++--------------
 lib/ngtcp2_conn.h            |  31 +-
 lib/ngtcp2_gaptr.c           |  22 +-
 lib/ngtcp2_log.c             |   6 +-
 lib/ngtcp2_pkt.c             |  42 ++-
 lib/ngtcp2_pq.c              |   7 +-
 lib/ngtcp2_pq.h              |  14 +-
 lib/ngtcp2_psl.c             |   4 +-
 lib/ngtcp2_psl.h             |   2 +-
 lib/ngtcp2_rtb.c             |  30 +-
 lib/ngtcp2_rtb.h             |  29 ++
 lib/ngtcp2_str.c             |   1 +
 lib/ngtcp2_strm.c            | 167 ++++++++-
 lib/ngtcp2_strm.h            |  43 ++-
 lib/ngtcp2_vec.c             | 138 +++++++
 lib/ngtcp2_vec.h             |  42 +++
 tests/CMakeLists.txt         |   2 +
 tests/Makefile.am            |   4 +
 tests/main.c                 |   8 +-
 tests/ngtcp2_conn_test.c     | 221 ++++++-----
 tests/ngtcp2_gaptr_test.c    |  38 +-
 tests/ngtcp2_idtr_test.c     |  14 +-
 tests/ngtcp2_pkt_test.c      |  47 ++-
 tests/ngtcp2_psl_test.c      |  39 +-
 tests/ngtcp2_strm_test.c     | 253 +++++++++++++
 tests/ngtcp2_strm_test.h     |  34 ++
 tests/ngtcp2_vec_test.c      | 224 +++++++++++
 tests/ngtcp2_vec_test.h      |  35 ++
 33 files changed, 1727 insertions(+), 492 deletions(-)
 create mode 100644 lib/ngtcp2_vec.c
 create mode 100644 lib/ngtcp2_vec.h
 create mode 100644 tests/ngtcp2_strm_test.c
 create mode 100644 tests/ngtcp2_strm_test.h
 create mode 100644 tests/ngtcp2_vec_test.c
 create mode 100644 tests/ngtcp2_vec_test.h

diff --git a/examples/client.cc b/examples/client.cc
index 95380b77..86174e73 100644
--- a/examples/client.cc
+++ b/examples/client.cc
@@ -499,7 +499,7 @@ int recv_crypto_data(ngtcp2_conn *conn, uint64_t offset, const uint8_t *data,
 } // namespace
 
 namespace {
-int recv_stream_data(ngtcp2_conn *conn, uint64_t stream_id, uint8_t fin,
+int recv_stream_data(ngtcp2_conn *conn, uint64_t stream_id, int fin,
                      uint64_t offset, const uint8_t *data, size_t datalen,
                      void *user_data, void *stream_user_data) {
   if (!config.quiet) {
diff --git a/examples/server.cc b/examples/server.cc
index ca0df292..a42d35dc 100644
--- a/examples/server.cc
+++ b/examples/server.cc
@@ -869,7 +869,7 @@ int recv_crypto_data(ngtcp2_conn *conn, uint64_t offset, const uint8_t *data,
 } // namespace
 
 namespace {
-int recv_stream_data(ngtcp2_conn *conn, uint64_t stream_id, uint8_t fin,
+int recv_stream_data(ngtcp2_conn *conn, uint64_t stream_id, int fin,
                      uint64_t offset, const uint8_t *data, size_t datalen,
                      void *user_data, void *stream_user_data) {
   auto h = static_cast<Handler *>(user_data);
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 683b31e0..288332fc 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -35,6 +35,7 @@ set(ngtcp2_SOURCES
   ngtcp2_pkt.c
   ngtcp2_conv.c
   ngtcp2_str.c
+  ngtcp2_vec.c
   ngtcp2_buf.c
   ngtcp2_conn.c
   ngtcp2_mem.c
diff --git a/lib/Makefile.am b/lib/Makefile.am
index a4a3e1af..2521ebd1 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -35,6 +35,7 @@ OBJECTS = \
 	ngtcp2_pkt.c \
 	ngtcp2_conv.c \
 	ngtcp2_str.c \
+	ngtcp2_vec.c \
 	ngtcp2_buf.c \
 	ngtcp2_conn.c \
 	ngtcp2_mem.c \
@@ -60,6 +61,7 @@ HFILES = \
 	ngtcp2_pkt.h \
 	ngtcp2_conv.h \
 	ngtcp2_str.h \
+	ngtcp2_vec.h \
 	ngtcp2_buf.h \
 	ngtcp2_conn.h \
 	ngtcp2_mem.h \
diff --git a/lib/includes/ngtcp2/ngtcp2.h b/lib/includes/ngtcp2/ngtcp2.h
index 1faa722d..f410819f 100644
--- a/lib/includes/ngtcp2/ngtcp2.h
+++ b/lib/includes/ngtcp2/ngtcp2.h
@@ -374,8 +374,12 @@ typedef struct {
   uint8_t fin;
   uint64_t stream_id;
   uint64_t offset;
-  size_t datalen;
-  const uint8_t *data;
+  /* datacnt is the number of elements that data contains.  Although
+     the length of data is 1 in this definition, the library may
+     allocate extra bytes to hold more elements. */
+  size_t datacnt;
+  /* data is the array of ngtcp2_vec which references data. */
+  ngtcp2_vec data[1];
 } ngtcp2_stream;
 
 typedef struct {
@@ -1027,7 +1031,7 @@ typedef ssize_t (*ngtcp2_encrypt_pn)(ngtcp2_conn *conn, uint8_t *dest,
                                      size_t noncelen, void *user_data);
 
 typedef int (*ngtcp2_recv_stream_data)(ngtcp2_conn *conn, uint64_t stream_id,
-                                       uint8_t fin, uint64_t offset,
+                                       int fin, uint64_t offset,
                                        const uint8_t *data, size_t datalen,
                                        void *user_data, void *stream_user_data);
 
diff --git a/lib/ngtcp2_conn.c b/lib/ngtcp2_conn.c
index c414f468..37d37c00 100644
--- a/lib/ngtcp2_conn.c
+++ b/lib/ngtcp2_conn.c
@@ -33,6 +33,7 @@
 #include "ngtcp2_log.h"
 #include "ngtcp2_cid.h"
 #include "ngtcp2_conv.h"
+#include "ngtcp2_vec.h"
 
 /*
  * conn_local_stream returns nonzero if |stream_id| indicates that it
@@ -78,7 +79,7 @@ static int conn_call_handshake_completed(ngtcp2_conn *conn) {
 }
 
 static int conn_call_recv_stream_data(ngtcp2_conn *conn, ngtcp2_strm *strm,
-                                      uint8_t fin, uint64_t offset,
+                                      int fin, uint64_t offset,
                                       const uint8_t *data, size_t datalen) {
   int rv;
 
@@ -170,6 +171,17 @@ static int pktns_init(ngtcp2_pktns *pktns, int delayed_ack, ngtcp2_cc_stat *ccs,
   return 0;
 }
 
+static int cycle_less(const ngtcp2_pq_entry *lhs, const ngtcp2_pq_entry *rhs) {
+  ngtcp2_strm *ls = ngtcp2_struct_of(lhs, ngtcp2_strm, pe);
+  ngtcp2_strm *rs = ngtcp2_struct_of(rhs, ngtcp2_strm, pe);
+
+  if (ls->cycle < rs->cycle) {
+    return rs->cycle - ls->cycle <= 1;
+  }
+
+  return ls->cycle - rs->cycle > 1;
+}
+
 static void pktns_free(ngtcp2_pktns *pktns, ngtcp2_mem *mem) {
   ngtcp2_frame_chain_list_del(pktns->frq, mem);
 
@@ -206,6 +218,8 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid,
     goto fail_strms_init;
   }
 
+  ngtcp2_pq_init(&(*pconn)->tx_strmq, cycle_less, mem);
+
   rv = ngtcp2_idtr_init(&(*pconn)->remote_bidi_idtr, !server, mem);
   if (rv != 0) {
     goto fail_remote_bidi_idtr_init;
@@ -380,6 +394,7 @@ void ngtcp2_conn_del(ngtcp2_conn *conn) {
 
   ngtcp2_idtr_free(&conn->remote_uni_idtr);
   ngtcp2_idtr_free(&conn->remote_bidi_idtr);
+  ngtcp2_pq_free(&conn->tx_strmq);
   ngtcp2_map_each_free(&conn->strms, delete_strms_each, conn->mem);
   ngtcp2_map_free(&conn->strms);
 
@@ -659,7 +674,8 @@ static size_t conn_retry_early_payloadlen(ngtcp2_conn *conn) {
 
   for (frc = conn->pktns.frq; frc; frc = frc->next) {
     assert(frc->fr.type == NGTCP2_FRAME_STREAM);
-    len += 1 /* Type */ + frc->fr.stream.datalen +
+    len += 1 /* Type */ +
+           ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt) +
            8 * 3 /* Stream ID, Offset, and Length */;
   }
 
@@ -705,45 +721,6 @@ static int conn_split_crypto_frame(ngtcp2_conn *conn, ngtcp2_frame_chain **pfrc,
   return 0;
 }
 
-/*
- * conn_split_stream_frame splits a STREAM frame (*pfrc)->fr.stream if
- * the length of payload is more then |left|.  The new
- * ngtcp2_frame_chain is inserted after the first item pointed by
- * |*pfrc|.
- */
-static int conn_split_stream_frame(ngtcp2_conn *conn, ngtcp2_frame_chain **pfrc,
-                                   size_t left) {
-  ngtcp2_stream *fr = &(*pfrc)->fr.stream, *nfr;
-  ngtcp2_frame_chain *nfrc;
-  int rv;
-
-  if (fr->datalen <= left) {
-    return 0;
-  }
-
-  rv = ngtcp2_frame_chain_new(&nfrc, conn->mem);
-  if (rv != 0) {
-    return rv;
-  }
-
-  nfr = &nfrc->fr.stream;
-  nfr->type = NGTCP2_FRAME_STREAM;
-  nfr->flags = 0;
-  nfr->fin = fr->fin;
-  nfr->stream_id = fr->stream_id;
-  nfr->offset = fr->offset + left;
-  nfr->datalen = fr->datalen - left;
-  nfr->data = fr->data + left;
-
-  fr->datalen = left;
-  fr->fin = 0;
-
-  nfrc->next = (*pfrc)->next;
-  (*pfrc)->next = nfrc;
-
-  return 0;
-}
-
 /*
  * conn_write_handshake_pkt writes handshake packet in the buffer
  * pointed by |dest| whose length is |destlen|.  |type| specifies long
@@ -766,7 +743,9 @@ static ssize_t conn_write_handshake_pkt(ngtcp2_conn *conn, uint8_t *dest,
   ngtcp2_ppe ppe;
   ngtcp2_pkt_hd hd;
   ngtcp2_frame_chain **pfrc;
+  ngtcp2_stream_frame_chain *nsfrc;
   ngtcp2_frame *ackfr = NULL, lfr;
+  ngtcp2_strm *strm;
   ssize_t spktlen;
   ngtcp2_crypto_ctx ctx;
   ngtcp2_rtb_entry *rtbent;
@@ -776,7 +755,6 @@ static ssize_t conn_write_handshake_pkt(ngtcp2_conn *conn, uint8_t *dest,
   size_t min_payloadlen;
   uint8_t flags = NGTCP2_RTB_FLAG_NONE;
   int pkt_empty = 1;
-  uint64_t written_stream_id = UINT64_MAX;
 
   switch (type) {
   case NGTCP2_PKT_INITIAL:
@@ -838,7 +816,8 @@ static ssize_t conn_write_handshake_pkt(ngtcp2_conn *conn, uint8_t *dest,
     }
   }
 
-  if (!pktns->frq && !ackfr) {
+  if (!pktns->frq && !ackfr &&
+      (type != NGTCP2_PKT_0RTT_PROTECTED || ngtcp2_pq_empty(&conn->tx_strmq))) {
     return 0;
   }
 
@@ -872,33 +851,19 @@ static ssize_t conn_write_handshake_pkt(ngtcp2_conn *conn, uint8_t *dest,
     switch ((*pfrc)->fr.type) {
     case NGTCP2_FRAME_CRYPTO:
       assert(type != NGTCP2_PKT_0RTT_PROTECTED);
-      if (ngtcp2_ppe_left(&ppe) <
-          NGTCP2_CRYPTO_OVERHEAD + NGTCP2_MIN_FRAME_PAYLOADLEN) {
+      left = ngtcp2_ppe_left(&ppe);
+      if (left < NGTCP2_CRYPTO_OVERHEAD + NGTCP2_MIN_FRAME_PAYLOADLEN) {
         goto frq_finish;
       }
 
-      left = ngtcp2_ppe_left(&ppe) - NGTCP2_CRYPTO_OVERHEAD;
+      left -= NGTCP2_CRYPTO_OVERHEAD;
       rv = conn_split_crypto_frame(conn, pfrc, left);
       if (rv != 0) {
         return rv;
       }
       break;
     case NGTCP2_FRAME_STREAM:
-      assert(type == NGTCP2_PKT_0RTT_PROTECTED);
-      if ((written_stream_id != UINT64_MAX &&
-           written_stream_id != (*pfrc)->fr.stream.stream_id) ||
-          ngtcp2_ppe_left(&ppe) <
-              NGTCP2_STREAM_OVERHEAD + NGTCP2_MIN_FRAME_PAYLOADLEN) {
-        goto frq_finish;
-      }
-
-      written_stream_id = (*pfrc)->fr.stream.stream_id;
-
-      left = ngtcp2_ppe_left(&ppe) - NGTCP2_STREAM_OVERHEAD;
-      rv = conn_split_stream_frame(conn, pfrc, left);
-      if (rv != 0) {
-        return rv;
-      }
+      assert(0);
       break;
     }
 
@@ -913,6 +878,41 @@ static ssize_t conn_write_handshake_pkt(ngtcp2_conn *conn, uint8_t *dest,
 
 frq_finish:
 
+  if (type == NGTCP2_PKT_0RTT_PROTECTED && *pfrc == NULL &&
+      !conn->pktns.tx_ckm) {
+    for (; !ngtcp2_pq_empty(&conn->tx_strmq);) {
+      strm = ngtcp2_conn_tx_strmq_top(conn);
+      if (ngtcp2_strm_streamfrq_empty(strm)) {
+        ngtcp2_conn_tx_strmq_pop(conn);
+        continue;
+      }
+
+      left = ngtcp2_ppe_left(&ppe);
+
+      if (left < NGTCP2_STREAM_OVERHEAD + NGTCP2_MIN_FRAME_PAYLOADLEN) {
+        break;
+      }
+
+      left -= NGTCP2_STREAM_OVERHEAD;
+      rv = ngtcp2_strm_streamfrq_pop(strm, &nsfrc, left);
+      if (rv != 0) {
+        return rv;
+      }
+
+      rv = conn_ppe_write_frame(conn, &ppe, &hd, &nsfrc->frc.fr);
+      if (rv != 0) {
+        assert(0);
+      }
+
+      *pfrc = &nsfrc->frc;
+      pfrc = &(*pfrc)->next;
+
+      pkt_empty = 0;
+
+      break;
+    }
+  }
+
   if (pkt_empty) {
     ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
                     "packet transmission canceled");
@@ -1293,17 +1293,17 @@ static ssize_t conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
   int rv;
   ngtcp2_ppe ppe;
   ngtcp2_pkt_hd hd;
-  ngtcp2_frame *ackfr;
+  ngtcp2_frame *ackfr = NULL;
   ssize_t nwrite;
   ngtcp2_crypto_ctx ctx;
   ngtcp2_frame_chain **pfrc, *nfrc, *frc;
+  ngtcp2_stream_frame_chain *nsfrc;
   ngtcp2_rtb_entry *ent;
-  ngtcp2_strm *strm, *strm_next;
+  ngtcp2_strm *strm;
   int pkt_empty = 1;
   ngtcp2_acktr_ack_entry *ack_ent = NULL;
   size_t ndatalen = 0;
   int send_stream = 0;
-  int ack_only;
   ngtcp2_pktns *pktns = &conn->pktns;
   size_t left;
   uint64_t written_stream_id = UINT64_MAX;
@@ -1317,6 +1317,7 @@ static ssize_t conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
     }
   }
 
+  /* TODO Take into account stream frames */
   if ((pktns->frq || send_stream || conn_should_send_max_data(conn)) &&
       conn->unsent_max_rx_offset > conn->max_rx_offset) {
     rv = ngtcp2_frame_chain_new(&nfrc, conn->mem);
@@ -1331,54 +1332,6 @@ static ssize_t conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
     conn->max_rx_offset = conn->unsent_max_rx_offset;
   }
 
-  while (conn->fc_strms) {
-    strm = conn->fc_strms;
-    rv = ngtcp2_frame_chain_new(&nfrc, conn->mem);
-    if (rv != 0) {
-      return rv;
-    }
-    nfrc->fr.type = NGTCP2_FRAME_MAX_STREAM_DATA;
-    nfrc->fr.max_stream_data.stream_id = strm->stream_id;
-    nfrc->fr.max_stream_data.max_stream_data = strm->unsent_max_rx_offset;
-    nfrc->next = pktns->frq;
-    pktns->frq = nfrc;
-
-    strm->max_rx_offset = strm->unsent_max_rx_offset;
-
-    strm_next = strm->fc_next;
-    conn->fc_strms = strm_next;
-    if (strm_next) {
-      strm_next->fc_pprev = &conn->fc_strms;
-    }
-    strm->fc_next = NULL;
-    strm->fc_pprev = NULL;
-  }
-
-  ack_only =
-      !send_stream &&
-      conn->unsent_max_remote_stream_id_bidi ==
-          conn->max_remote_stream_id_bidi &&
-      conn->unsent_max_remote_stream_id_uni == conn->max_remote_stream_id_uni &&
-      pktns->frq == NULL;
-
-  if (ack_only && conn->rcs.probe_pkt_left) {
-    /* Sending ACK only packet does not elicit ACK, therefore it is
-       not suitable for probe packet. */
-    return 0;
-  }
-
-  ackfr = NULL;
-  rv = conn_create_ack_frame(conn, &ackfr, &pktns->acktr, ts,
-                             !ack_only /* nodelay */,
-                             conn->local_settings.ack_delay_exponent);
-  if (rv != 0) {
-    return rv;
-  }
-
-  if (ackfr == NULL && ack_only) {
-    return 0;
-  }
-
   ngtcp2_pkt_hd_init(
       &hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_SHORT, &conn->dcid, &conn->scid,
       pktns->last_tx_pkt_num + 1,
@@ -1397,21 +1350,7 @@ static ssize_t conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
 
   rv = ngtcp2_ppe_encode_hd(&ppe, &hd);
   if (rv != 0) {
-    goto fail;
-  }
-
-  if (ackfr) {
-    rv = conn_ppe_write_frame(conn, &ppe, &hd, ackfr);
-    if (rv != 0) {
-      goto fail;
-    }
-    ngtcp2_acktr_commit_ack(&pktns->acktr);
-    pkt_empty = 0;
-
-    ack_ent = ngtcp2_acktr_add_ack(&pktns->acktr, hd.pkt_num, &ackfr->ack, ts,
-                                   0 /*ack_only*/);
-    /* Now ackfr is owned by conn->acktr. */
-    ackfr = NULL;
+    return rv;
   }
 
   for (pfrc = &pktns->frq; *pfrc;) {
@@ -1436,28 +1375,7 @@ static ssize_t conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
       }
       break;
     case NGTCP2_FRAME_STREAM:
-      strm = ngtcp2_conn_find_stream(conn, (*pfrc)->fr.stream.stream_id);
-      if (strm == NULL || (strm->flags & NGTCP2_STRM_FLAG_SENT_RST)) {
-        frc = *pfrc;
-        *pfrc = (*pfrc)->next;
-        ngtcp2_frame_chain_del(frc, conn->mem);
-        continue;
-      }
-
-      if ((written_stream_id != UINT64_MAX &&
-           written_stream_id != (*pfrc)->fr.stream.stream_id) ||
-          ngtcp2_ppe_left(&ppe) <
-              NGTCP2_STREAM_OVERHEAD + NGTCP2_MIN_FRAME_PAYLOADLEN) {
-        goto frq_finish;
-      }
-
-      written_stream_id = (*pfrc)->fr.stream.stream_id;
-
-      rv = conn_split_stream_frame(
-          conn, pfrc, ngtcp2_ppe_left(&ppe) - NGTCP2_STREAM_OVERHEAD);
-      if (rv != 0) {
-        return rv;
-      }
+      assert(0);
       break;
     case NGTCP2_FRAME_MAX_STREAM_ID: {
       int cancel;
@@ -1479,7 +1397,7 @@ static ssize_t conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
     case NGTCP2_FRAME_MAX_STREAM_DATA:
       strm =
           ngtcp2_conn_find_stream(conn, (*pfrc)->fr.max_stream_data.stream_id);
-      if (strm == NULL ||
+      if (strm == NULL || (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) ||
           (*pfrc)->fr.max_stream_data.max_stream_data < strm->max_rx_offset) {
         frc = *pfrc;
         *pfrc = (*pfrc)->next;
@@ -1496,13 +1414,13 @@ static ssize_t conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
       }
       break;
     case NGTCP2_FRAME_CRYPTO:
-      if (ngtcp2_ppe_left(&ppe) <
-          NGTCP2_CRYPTO_OVERHEAD + NGTCP2_MIN_FRAME_PAYLOADLEN) {
+      left = ngtcp2_ppe_left(&ppe);
+      if (left < NGTCP2_CRYPTO_OVERHEAD + NGTCP2_MIN_FRAME_PAYLOADLEN) {
         goto frq_finish;
       }
 
-      rv = conn_split_crypto_frame(
-          conn, pfrc, ngtcp2_ppe_left(&ppe) - NGTCP2_CRYPTO_OVERHEAD);
+      left -= NGTCP2_CRYPTO_OVERHEAD;
+      rv = conn_split_crypto_frame(conn, pfrc, left);
       if (rv != 0) {
         return rv;
       }
@@ -1523,8 +1441,9 @@ frq_finish:
 
   /* Write MAX_STREAM_ID after RST_STREAM so that we can extend stream
      ID space in one packet. */
-  if (*pfrc == NULL && conn->unsent_max_remote_stream_id_bidi >
-                           conn->max_remote_stream_id_bidi) {
+  if (rv != NGTCP2_ERR_NOBUF && *pfrc == NULL &&
+      conn->unsent_max_remote_stream_id_bidi >
+          conn->max_remote_stream_id_bidi) {
     rv = ngtcp2_frame_chain_new(&nfrc, conn->mem);
     if (rv != 0) {
       return rv;
@@ -1545,7 +1464,7 @@ frq_finish:
     }
   }
 
-  if (*pfrc == NULL &&
+  if (rv != NGTCP2_ERR_NOBUF && *pfrc == NULL &&
       conn->unsent_max_remote_stream_id_uni > conn->max_remote_stream_id_uni) {
     rv = ngtcp2_frame_chain_new(&nfrc, conn->mem);
     if (rv != 0) {
@@ -1567,9 +1486,82 @@ frq_finish:
     }
   }
 
+  if (rv != NGTCP2_ERR_NOBUF && *pfrc == NULL) {
+    for (; !ngtcp2_pq_empty(&conn->tx_strmq);) {
+      strm = ngtcp2_conn_tx_strmq_top(conn);
+
+      if (!(strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) &&
+          strm->max_rx_offset < strm->unsent_max_rx_offset) {
+        rv = ngtcp2_frame_chain_new(&nfrc, conn->mem);
+        if (rv != 0) {
+          return rv;
+        }
+        nfrc->fr.type = NGTCP2_FRAME_MAX_STREAM_DATA;
+        nfrc->fr.max_stream_data.stream_id = strm->stream_id;
+        nfrc->fr.max_stream_data.max_stream_data = strm->unsent_max_rx_offset;
+        ngtcp2_list_insert(nfrc, pfrc);
+
+        rv = conn_ppe_write_frame(conn, &ppe, &hd, &nfrc->fr);
+        if (rv != 0) {
+          assert(NGTCP2_ERR_NOBUF == rv);
+          break;
+        }
+
+        pkt_empty = 0;
+        pfrc = &(*pfrc)->next;
+        strm->max_rx_offset = strm->unsent_max_rx_offset;
+      }
+
+      for (;;) {
+        if (ngtcp2_strm_streamfrq_empty(strm)) {
+          ngtcp2_conn_tx_strmq_pop(conn);
+          if (written_stream_id == UINT64_MAX) {
+            break;
+          }
+          goto tx_strmq_finish;
+        }
+
+        left = ngtcp2_ppe_left(&ppe);
+
+        if (left < NGTCP2_STREAM_OVERHEAD + NGTCP2_MIN_FRAME_PAYLOADLEN) {
+          if (written_stream_id != UINT64_MAX) {
+            ngtcp2_conn_tx_strmq_pop(conn);
+            ++strm->cycle;
+            rv = ngtcp2_conn_tx_strmq_push(conn, strm);
+            if (rv != 0) {
+              return rv;
+            }
+          }
+          goto tx_strmq_finish;
+        }
+
+        left -= NGTCP2_STREAM_OVERHEAD;
+        rv = ngtcp2_strm_streamfrq_pop(strm, &nsfrc, left);
+        if (rv != 0) {
+          assert(ngtcp2_err_is_fatal(rv));
+          return rv;
+        }
+
+        rv = conn_ppe_write_frame(conn, &ppe, &hd, &nsfrc->frc.fr);
+        if (rv != 0) {
+          assert(0);
+        }
+
+        *pfrc = &nsfrc->frc;
+        pfrc = &(*pfrc)->next;
+
+        written_stream_id = strm->stream_id;
+
+        pkt_empty = 0;
+      }
+    }
+  }
+
+tx_strmq_finish:
+
   left = ngtcp2_ppe_left(&ppe);
 
-  if (send_stream &&
+  if (rv != NGTCP2_ERR_NOBUF && send_stream &&
       (written_stream_id == UINT64_MAX ||
        written_stream_id == data_strm->stream_id) &&
       *pfrc == NULL &&
@@ -1580,41 +1572,83 @@ frq_finish:
 
     fin = fin && ndatalen == datalen;
 
-    rv = ngtcp2_frame_chain_new(&nfrc, conn->mem);
+    rv = ngtcp2_stream_frame_chain_new(&nsfrc, conn->mem);
     if (rv != 0) {
+      assert(ngtcp2_err_is_fatal(rv));
       return rv;
     }
 
-    nfrc->fr.type = NGTCP2_FRAME_STREAM;
-    nfrc->fr.stream.flags = 0;
-    nfrc->fr.stream.fin = fin;
-    nfrc->fr.stream.stream_id = data_strm->stream_id;
-    nfrc->fr.stream.offset = data_strm->tx_offset;
-    nfrc->fr.stream.datalen = ndatalen;
-    nfrc->fr.stream.data = data;
+    nsfrc->fr.type = NGTCP2_FRAME_STREAM;
+    nsfrc->fr.flags = 0;
+    nsfrc->fr.fin = fin;
+    nsfrc->fr.stream_id = data_strm->stream_id;
+    nsfrc->fr.offset = data_strm->tx_offset;
+    if (ndatalen) {
+      nsfrc->fr.datacnt = 1;
+      nsfrc->fr.data[0].len = ndatalen;
+      nsfrc->fr.data[0].base = (uint8_t *)data;
+    } else {
+      nsfrc->fr.datacnt = 0;
+    }
 
-    rv = conn_ppe_write_frame(conn, &ppe, &hd, &nfrc->fr);
+    rv = conn_ppe_write_frame(conn, &ppe, &hd, &nsfrc->frc.fr);
     if (rv != 0) {
-      assert(NGTCP2_ERR_NOBUF == rv);
-      ngtcp2_frame_chain_del(nfrc, conn->mem);
-      send_stream = 0;
+      assert(0);
+    }
+
+    *pfrc = &nsfrc->frc;
+    pfrc = &(*pfrc)->next;
+
+    pkt_empty = 0;
+  } else {
+    send_stream = 0;
+  }
+
+  if (pkt_empty && conn->rcs.probe_pkt_left) {
+    /* Sending ACK only packet does not elicit ACK, therefore it is
+       not suitable for probe packet. */
+    ngtcp2_log_tx_cancel(&conn->log, &hd);
+    return rv;
+  }
+
+  rv = conn_create_ack_frame(conn, &ackfr, &pktns->acktr, ts,
+                             !pkt_empty /* nodelay */,
+                             conn->local_settings.ack_delay_exponent);
+  if (rv != 0) {
+    assert(ngtcp2_err_is_fatal(rv));
+    return rv;
+  }
+
+  if (ackfr) {
+    rv = conn_ppe_write_frame(conn, &ppe, &hd, ackfr);
+    if (rv != 0) {
+      assert(rv == NGTCP2_ERR_NOBUF);
+      ngtcp2_mem_free(conn->mem, ackfr);
     } else {
-      *pfrc = nfrc;
-      pfrc = &(*pfrc)->next;
+      ngtcp2_acktr_commit_ack(&pktns->acktr);
+      pkt_empty = 0;
 
+      ack_ent = ngtcp2_acktr_add_ack(&pktns->acktr, hd.pkt_num, &ackfr->ack, ts,
+                                     0 /*ack_only*/);
+      /* Now ackfr is owned by conn->acktr. */
       pkt_empty = 0;
     }
-  } else {
-    send_stream = 0;
+    ackfr = NULL;
   }
 
   if (pkt_empty) {
+    /* TODO We might have NGTCP2_ERR_NOBUF before creating ACK
+       frame */
     ngtcp2_log_tx_cancel(&conn->log, &hd);
-    return rv;
+    return 0;
   }
 
+  /* TODO Push STREAM frame back to ngtcp2_strm if there is an error
+     before ngtcp2_rtb_entry is safely created and added. */
+
   nwrite = ngtcp2_ppe_final(&ppe, NULL);
   if (nwrite < 0) {
+    assert(ngtcp2_err_is_fatal((int)nwrite));
     return nwrite;
   }
 
@@ -1622,6 +1656,7 @@ frq_finish:
     rv = ngtcp2_rtb_entry_new(&ent, &hd, NULL, ts, (size_t)nwrite,
                               NGTCP2_RTB_FLAG_NONE, conn->mem);
     if (rv != 0) {
+      assert(ngtcp2_err_is_fatal((int)nwrite));
       return rv;
     }
 
@@ -1654,10 +1689,6 @@ frq_finish:
   ++pktns->last_tx_pkt_num;
 
   return nwrite;
-
-fail:
-  ngtcp2_mem_free(conn->mem, ackfr);
-  return rv;
 }
 
 /*
@@ -2088,6 +2119,52 @@ static int conn_on_version_negotiation(ngtcp2_conn *conn,
   return 0;
 }
 
+/*
+ * conn_resched_frames reschedules frames linked from |*pfrc| for
+ * retransmission.
+ */
+static int conn_resched_frames(ngtcp2_conn *conn, ngtcp2_pktns *pktns,
+                               ngtcp2_frame_chain **pfrc) {
+  ngtcp2_frame_chain **first = pfrc;
+  ngtcp2_stream_frame_chain *frc;
+  ngtcp2_stream *fr;
+  ngtcp2_strm *strm;
+  int rv;
+
+  for (; *pfrc;) {
+    if ((*pfrc)->fr.type == NGTCP2_FRAME_STREAM) {
+      frc = (ngtcp2_stream_frame_chain *)*pfrc;
+      *pfrc = frc->next;
+      frc->next = NULL;
+      fr = &frc->fr;
+
+      strm = ngtcp2_conn_find_stream(conn, fr->stream_id);
+      if (!strm) {
+        ngtcp2_stream_frame_chain_del(frc, conn->mem);
+        continue;
+      }
+      rv = ngtcp2_strm_streamfrq_push(strm, frc);
+      if (rv != 0) {
+        ngtcp2_stream_frame_chain_del(frc, conn->mem);
+        return rv;
+      }
+      if (!ngtcp2_strm_is_tx_queued(strm)) {
+        rv = ngtcp2_conn_tx_strmq_push(conn, strm);
+        if (rv != 0) {
+          return rv;
+        }
+      }
+      continue;
+    }
+    pfrc = &(*pfrc)->next;
+  }
+
+  *pfrc = pktns->frq;
+  pktns->frq = *first;
+
+  return 0;
+}
+
 /*
  * conn_on_retry is called when Retry packet is received.  The
  * function decodes the data in the buffer pointed by |payload| whose
@@ -2113,6 +2190,7 @@ static int conn_on_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,
   uint8_t *p;
   ngtcp2_rtb *rtb = &conn->pktns.rtb;
   uint8_t cidbuf[sizeof(retry.odcid.data) * 2 + 1];
+  ngtcp2_frame_chain *frc = NULL;
 
   if (conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) {
     return 0;
@@ -2151,8 +2229,17 @@ static int conn_on_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,
   ngtcp2_crypto_km_del(conn->early_ckm, conn->mem);
   conn->early_ckm = NULL;
 
-  rv = ngtcp2_rtb_remove_all(rtb, &conn->pktns.frq);
+  rv = ngtcp2_rtb_remove_all(rtb, &frc);
   if (rv != 0) {
+    assert(ngtcp2_err_is_fatal(rv));
+    ngtcp2_frame_chain_list_del(frc, conn->mem);
+    return rv;
+  }
+
+  rv = conn_resched_frames(conn, &conn->pktns, &frc);
+  if (rv != 0) {
+    assert(ngtcp2_err_is_fatal(rv));
+    ngtcp2_frame_chain_list_del(frc, conn->mem);
     return rv;
   }
 
@@ -2184,10 +2271,28 @@ static int conn_on_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,
   return 0;
 }
 
-static int pktns_detect_lost_pkt(ngtcp2_pktns *pktns, ngtcp2_rcvry_stat *rcs,
-                                 uint64_t largest_ack, ngtcp2_tstamp ts) {
-  return ngtcp2_rtb_detect_lost_pkt(&pktns->rtb, &pktns->frq, rcs, largest_ack,
-                                    pktns->last_tx_pkt_num, ts);
+int ngtcp2_conn_detect_lost_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns,
+                                ngtcp2_rcvry_stat *rcs, uint64_t largest_ack,
+                                ngtcp2_tstamp ts) {
+  ngtcp2_frame_chain *frc = NULL;
+  int rv;
+
+  rv = ngtcp2_rtb_detect_lost_pkt(&pktns->rtb, &frc, rcs, largest_ack,
+                                  pktns->last_tx_pkt_num, ts);
+  if (rv != 0) {
+    /* TODO assert this */
+    assert(ngtcp2_err_is_fatal(rv));
+    ngtcp2_frame_chain_list_del(frc, conn->mem);
+    return rv;
+  }
+
+  rv = conn_resched_frames(conn, pktns, &frc);
+  if (rv != 0) {
+    ngtcp2_frame_chain_list_del(frc, conn->mem);
+    return rv;
+  }
+
+  return 0;
 }
 
 static int pktns_pkt_lost(ngtcp2_pktns *pktns) {
@@ -2211,6 +2316,7 @@ static int conn_recv_ack(ngtcp2_conn *conn, ngtcp2_pktns *pktns,
                          const ngtcp2_pkt_hd *hd, ngtcp2_ack *fr,
                          ngtcp2_tstamp ts) {
   int rv;
+  ngtcp2_frame_chain *frc = NULL;
 
   rv = ngtcp2_pkt_validate_ack(fr);
   if (rv != 0) {
@@ -2222,15 +2328,27 @@ static int conn_recv_ack(ngtcp2_conn *conn, ngtcp2_pktns *pktns,
     return rv;
   }
 
-  rv = ngtcp2_rtb_recv_ack(&pktns->rtb, &pktns->frq, hd, fr, conn, ts);
+  rv = ngtcp2_rtb_recv_ack(&pktns->rtb, &frc, hd, fr, conn, ts);
+  if (rv != 0) {
+    /* TODO assert this */
+    assert(ngtcp2_err_is_fatal(rv));
+    ngtcp2_frame_chain_list_del(frc, conn->mem);
+    return rv;
+  }
+
+  /* TODO We don't need to do this for Initial and Handshake packet
+     because they don't include STREAM frame. */
+  rv = conn_resched_frames(conn, pktns, &frc);
   if (rv != 0) {
+    ngtcp2_frame_chain_list_del(frc, conn->mem);
     return rv;
   }
 
   if (!ngtcp2_pkt_handshake_pkt(hd)) {
     conn->largest_ack = ngtcp2_max(conn->largest_ack, (int64_t)fr->largest_ack);
 
-    rv = pktns_detect_lost_pkt(pktns, &conn->rcs, fr->largest_ack, ts);
+    rv = ngtcp2_conn_detect_lost_pkt(conn, pktns, &conn->rcs, fr->largest_ack,
+                                     ts);
     if (rv != 0) {
       return rv;
     }
@@ -2510,24 +2628,6 @@ static ssize_t conn_decrypt_pn(ngtcp2_conn *conn, ngtcp2_pkt_hd *hd,
   return p - dest;
 }
 
-static void conn_extend_max_stream_offset(ngtcp2_conn *conn, ngtcp2_strm *strm,
-                                          size_t datalen) {
-  if (strm->unsent_max_rx_offset <= NGTCP2_MAX_VARINT - datalen) {
-    strm->unsent_max_rx_offset += datalen;
-  }
-
-  if (!(strm->flags &
-        (NGTCP2_STRM_FLAG_SHUT_RD | NGTCP2_STRM_FLAG_STOP_SENDING)) &&
-      !strm->fc_pprev && conn_should_send_max_stream_data(conn, strm)) {
-    strm->fc_pprev = &conn->fc_strms;
-    if (conn->fc_strms) {
-      strm->fc_next = conn->fc_strms;
-      conn->fc_strms->fc_pprev = &strm->fc_next;
-    }
-    conn->fc_strms = strm;
-  }
-}
-
 /*
  * conn_emit_pending_crypto_data delivers pending stream data to the
  * application due to packet reordering.
@@ -2661,6 +2761,7 @@ static int pktns_pkt_num_is_duplicate(ngtcp2_pktns *pktns, uint64_t pkt_num) {
 static int pktns_commit_recv_pkt_num(ngtcp2_pktns *pktns, uint64_t pkt_num) {
   int rv;
   ngtcp2_psl_it it;
+  ngtcp2_range key;
 
   rv = ngtcp2_gaptr_push(&pktns->pngap, pkt_num, 1);
   if (rv != 0) {
@@ -2669,7 +2770,8 @@ static int pktns_commit_recv_pkt_num(ngtcp2_pktns *pktns, uint64_t pkt_num) {
 
   if (ngtcp2_psl_len(&pktns->pngap.gap) > 256) {
     it = ngtcp2_psl_begin(&pktns->pngap.gap);
-    return ngtcp2_psl_remove(&pktns->pngap.gap, NULL, ngtcp2_psl_it_range(&it));
+    key = ngtcp2_psl_it_range(&it);
+    return ngtcp2_psl_remove(&pktns->pngap.gap, NULL, &key);
   }
 
   return 0;
@@ -3288,6 +3390,7 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) {
   uint64_t rx_offset, fr_end_offset;
   int local_stream;
   int bidi;
+  size_t datalen = ngtcp2_vec_len(fr->data, fr->datacnt);
 
   local_stream = conn_local_stream(conn, fr->stream_id);
   bidi = bidi_stream(fr->stream_id);
@@ -3313,7 +3416,7 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) {
     idtr = &conn->remote_uni_idtr;
   }
 
-  if (NGTCP2_MAX_VARINT - fr->datalen < fr->offset) {
+  if (NGTCP2_MAX_VARINT - datalen < fr->offset) {
     return NGTCP2_ERR_PROTO;
   }
 
@@ -3347,20 +3450,20 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) {
     }
   }
 
-  fr_end_offset = fr->offset + fr->datalen;
+  fr_end_offset = fr->offset + datalen;
 
   if (strm->max_rx_offset < fr_end_offset) {
     return NGTCP2_ERR_FLOW_CONTROL;
   }
 
   if (strm->last_rx_offset < fr_end_offset) {
-    size_t datalen = fr_end_offset - strm->last_rx_offset;
+    size_t len = fr_end_offset - strm->last_rx_offset;
 
-    if (conn_max_data_violated(conn, datalen)) {
+    if (conn_max_data_violated(conn, len)) {
       return NGTCP2_ERR_FLOW_CONTROL;
     }
 
-    conn->rx_offset += datalen;
+    conn->rx_offset += len;
   }
 
   rx_offset = ngtcp2_strm_rx_offset(strm);
@@ -3382,17 +3485,6 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) {
                                                      strm->app_error_code);
       }
 
-      /* Since strm is now in closed (remote), we don't have to send
-         MAX_STREAM_DATA anymore. */
-      if (strm->fc_pprev) {
-        *strm->fc_pprev = strm->fc_next;
-        if (strm->fc_next) {
-          strm->fc_next->fc_pprev = strm->fc_pprev;
-        }
-        strm->fc_pprev = NULL;
-        strm->fc_next = NULL;
-      }
-
       if (fr_end_offset == rx_offset) {
         rv = conn_call_recv_stream_data(conn, strm, 1, rx_offset, NULL, 0);
         if (rv != 0) {
@@ -3421,30 +3513,41 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) {
 
   if (fr->offset <= rx_offset) {
     size_t ncut = rx_offset - fr->offset;
-    const uint8_t *data = fr->data + ncut;
-    size_t datalen = fr->datalen - ncut;
     uint64_t offset = rx_offset;
+    const uint8_t *data;
+    int fin;
 
-    rx_offset += datalen;
-    rv = ngtcp2_rob_remove_prefix(&strm->rob, rx_offset);
-    if (rv != 0) {
-      return rv;
-    }
+    if (fr->datacnt) {
+      data = fr->data[0].base + ncut;
+      datalen -= ncut;
 
-    rv = conn_call_recv_stream_data(conn, strm,
-                                    (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) &&
-                                        rx_offset == strm->last_rx_offset,
-                                    offset, data, datalen);
-    if (rv != 0) {
-      return rv;
+      rx_offset += datalen;
+      rv = ngtcp2_rob_remove_prefix(&strm->rob, rx_offset);
+      if (rv != 0) {
+        return rv;
+      }
+    } else {
+      data = NULL;
+      datalen = 0;
     }
 
-    rv = conn_emit_pending_stream_data(conn, strm, rx_offset);
-    if (rv != 0) {
-      return rv;
+    fin = (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) &&
+          rx_offset == strm->last_rx_offset;
+
+    if (fin || datalen) {
+      rv = conn_call_recv_stream_data(conn, strm, fin, offset, data, datalen);
+      if (rv != 0) {
+        return rv;
+      }
+
+      rv = conn_emit_pending_stream_data(conn, strm, rx_offset);
+      if (rv != 0) {
+        return rv;
+      }
     }
-  } else {
-    rv = ngtcp2_strm_recv_reordering(strm, fr->data, fr->datalen, fr->offset);
+  } else if (fr->datacnt) {
+    rv = ngtcp2_strm_recv_reordering(strm, fr->data[0].base, fr->data[0].len,
+                                     fr->offset);
     if (rv != 0) {
       return rv;
     }
@@ -3503,17 +3606,6 @@ static int conn_stop_sending(ngtcp2_conn *conn, ngtcp2_strm *strm,
   frc->next = pktns->frq;
   pktns->frq = frc;
 
-  /* Since STREAM is being reset, we don't have to send
-     MAX_STREAM_DATA anymore */
-  if (strm->fc_pprev) {
-    *strm->fc_pprev = strm->fc_next;
-    if (strm->fc_next) {
-      strm->fc_next->fc_pprev = strm->fc_pprev;
-    }
-    strm->fc_pprev = NULL;
-    strm->fc_next = NULL;
-  }
-
   return 0;
 }
 
@@ -3629,6 +3721,8 @@ static int conn_recv_stop_sending(ngtcp2_conn *conn,
 
   strm->flags |= NGTCP2_STRM_FLAG_SHUT_WR | NGTCP2_STRM_FLAG_SENT_RST;
 
+  ngtcp2_strm_streamfrq_clear(strm);
+
   return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm, fr->app_error_code);
 }
 
@@ -4093,7 +4187,8 @@ static ssize_t conn_recv_pkt(ngtcp2_conn *conn, const uint8_t *pkt,
       if (rv != 0) {
         return rv;
       }
-      conn_update_rx_bw(conn, fr->stream.datalen, ts);
+      conn_update_rx_bw(
+          conn, ngtcp2_vec_len(fr->stream.data, fr->stream.datacnt), ts);
       break;
     case NGTCP2_FRAME_CRYPTO:
       rv = conn_recv_crypto(conn, pktns->crypto_rx_offset_base,
@@ -4402,7 +4497,7 @@ static ssize_t conn_handshake(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
     }
 
     if (conn->state == NGTCP2_CS_CLIENT_INITIAL) {
-      require_padding = !conn->pktns.frq;
+      require_padding = ngtcp2_pq_empty(&conn->tx_strmq);
       nwrite =
           conn_write_client_initial(conn, dest, destlen, require_padding, ts);
       if (nwrite < 0) {
@@ -4617,7 +4712,7 @@ static ssize_t conn_write_stream_early(ngtcp2_conn *conn, uint8_t *dest,
   ngtcp2_crypto_ctx ctx;
   ngtcp2_ppe ppe;
   ngtcp2_rtb_entry *ent;
-  ngtcp2_frame_chain *frc;
+  ngtcp2_stream_frame_chain *frc;
   ngtcp2_frame localfr;
   ngtcp2_pkt_hd hd;
   int rv;
@@ -4671,26 +4766,31 @@ static ssize_t conn_write_stream_early(ngtcp2_conn *conn, uint8_t *dest,
 
   fin = fin && ndatalen == datalen;
 
-  rv = ngtcp2_frame_chain_new(&frc, conn->mem);
+  rv = ngtcp2_stream_frame_chain_new(&frc, conn->mem);
   if (rv != 0) {
     return rv;
   }
 
   frc->fr.type = NGTCP2_FRAME_STREAM;
-  frc->fr.stream.flags = 0;
-  frc->fr.stream.fin = fin;
-  frc->fr.stream.stream_id = strm->stream_id;
-  frc->fr.stream.offset = strm->tx_offset;
-  frc->fr.stream.datalen = ndatalen;
-  frc->fr.stream.data = data;
+  frc->fr.flags = 0;
+  frc->fr.fin = fin;
+  frc->fr.stream_id = strm->stream_id;
+  frc->fr.offset = strm->tx_offset;
+  if (ndatalen) {
+    frc->fr.datacnt = 1;
+    frc->fr.data[0].len = ndatalen;
+    frc->fr.data[0].base = (uint8_t *)data;
+  } else {
+    frc->fr.datacnt = 0;
+  }
 
-  rv = ngtcp2_ppe_encode_frame(&ppe, &frc->fr);
+  rv = ngtcp2_ppe_encode_frame(&ppe, &frc->frc.fr);
   if (rv != 0) {
-    ngtcp2_frame_chain_del(frc, conn->mem);
+    ngtcp2_stream_frame_chain_del(frc, conn->mem);
     return rv;
   }
 
-  ngtcp2_log_tx_fr(&conn->log, &hd, &frc->fr);
+  ngtcp2_log_tx_fr(&conn->log, &hd, &frc->frc.fr);
 
   if (require_padding) {
     localfr.type = NGTCP2_FRAME_PADDING;
@@ -4701,14 +4801,14 @@ static ssize_t conn_write_stream_early(ngtcp2_conn *conn, uint8_t *dest,
 
   nwrite = ngtcp2_ppe_final(&ppe, NULL);
   if (nwrite < 0) {
-    ngtcp2_frame_chain_del(frc, conn->mem);
+    ngtcp2_stream_frame_chain_del(frc, conn->mem);
     return nwrite;
   }
 
-  rv = ngtcp2_rtb_entry_new(&ent, &hd, frc, ts, (size_t)nwrite,
+  rv = ngtcp2_rtb_entry_new(&ent, &hd, &frc->frc, ts, (size_t)nwrite,
                             NGTCP2_RTB_FLAG_NONE, conn->mem);
   if (rv != 0) {
-    ngtcp2_frame_chain_del(frc, conn->mem);
+    ngtcp2_stream_frame_chain_del(frc, conn->mem);
     return rv;
   }
 
@@ -5353,6 +5453,7 @@ ssize_t ngtcp2_conn_write_stream(ngtcp2_conn *conn, uint8_t *dest,
 
     nwrite = conn_write_pkt(conn, dest, ngtcp2_min(destlen, cwnd), pdatalen,
                             strm, fin, data, datalen, ts);
+
     if (nwrite) {
       return nwrite;
     }
@@ -5522,11 +5623,8 @@ int ngtcp2_conn_close_stream(ngtcp2_conn *conn, ngtcp2_strm *strm,
     }
   }
 
-  if (strm->fc_pprev) {
-    *strm->fc_pprev = strm->fc_next;
-    if (strm->fc_next) {
-      strm->fc_next->fc_pprev = strm->fc_pprev;
-    }
+  if (ngtcp2_strm_is_tx_queued(strm)) {
+    ngtcp2_pq_remove(&conn->tx_strmq, &strm->pe);
   }
 
   ngtcp2_strm_free(strm);
@@ -5560,6 +5658,8 @@ static int conn_shutdown_stream_write(ngtcp2_conn *conn, ngtcp2_strm *strm,
   strm->flags |= NGTCP2_STRM_FLAG_SHUT_WR | NGTCP2_STRM_FLAG_SENT_RST;
   strm->app_error_code = app_error_code;
 
+  ngtcp2_strm_streamfrq_clear(strm);
+
   return conn_rst_stream(conn, strm, app_error_code);
 }
 
@@ -5634,6 +5734,28 @@ int ngtcp2_conn_shutdown_stream_read(ngtcp2_conn *conn, uint64_t stream_id,
   return conn_shutdown_stream_read(conn, strm, app_error_code);
 }
 
+static int conn_extend_max_stream_offset(ngtcp2_conn *conn, ngtcp2_strm *strm,
+                                         size_t datalen) {
+  ngtcp2_strm *top;
+
+  if (strm->unsent_max_rx_offset <= NGTCP2_MAX_VARINT - datalen) {
+    strm->unsent_max_rx_offset += datalen;
+  }
+
+  if (!(strm->flags &
+        (NGTCP2_STRM_FLAG_SHUT_RD | NGTCP2_STRM_FLAG_STOP_SENDING)) &&
+      !ngtcp2_strm_is_tx_queued(strm) &&
+      conn_should_send_max_stream_data(conn, strm)) {
+    if (!ngtcp2_pq_empty(&conn->tx_strmq)) {
+      top = ngtcp2_conn_tx_strmq_top(conn);
+      strm->cycle = top->cycle;
+    }
+    return ngtcp2_conn_tx_strmq_push(conn, strm);
+  }
+
+  return 0;
+}
+
 int ngtcp2_conn_extend_max_stream_offset(ngtcp2_conn *conn, uint64_t stream_id,
                                          size_t datalen) {
   ngtcp2_strm *strm;
@@ -5643,9 +5765,7 @@ int ngtcp2_conn_extend_max_stream_offset(ngtcp2_conn *conn, uint64_t stream_id,
     return NGTCP2_ERR_STREAM_NOT_FOUND;
   }
 
-  conn_extend_max_stream_offset(conn, strm, datalen);
-
-  return 0;
+  return conn_extend_max_stream_offset(conn, strm, datalen);
 }
 
 void ngtcp2_conn_extend_max_offset(ngtcp2_conn *conn, size_t datalen) {
@@ -5682,10 +5802,26 @@ uint32_t ngtcp2_conn_get_negotiated_version(ngtcp2_conn *conn) {
 int ngtcp2_conn_early_data_rejected(ngtcp2_conn *conn) {
   ngtcp2_pktns *pktns = &conn->pktns;
   ngtcp2_rtb *rtb = &conn->pktns.rtb;
+  ngtcp2_frame_chain *frc = NULL;
+  int rv;
 
   conn->flags |= NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED;
 
-  return ngtcp2_rtb_remove_all(rtb, &pktns->frq);
+  rv = ngtcp2_rtb_remove_all(rtb, &frc);
+  if (rv != 0) {
+    assert(ngtcp2_err_is_fatal(rv));
+    ngtcp2_frame_chain_list_del(frc, conn->mem);
+    return rv;
+  }
+
+  rv = conn_resched_frames(conn, pktns, &frc);
+  if (rv != 0) {
+    assert(ngtcp2_err_is_fatal(rv));
+    ngtcp2_frame_chain_list_del(frc, conn->mem);
+    return rv;
+  }
+
+  return rv;
 }
 
 void ngtcp2_conn_update_rtt(ngtcp2_conn *conn, uint64_t rtt,
@@ -5814,7 +5950,8 @@ int ngtcp2_conn_on_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
     }
     ++rcs->handshake_count;
   } else if (rcs->loss_time) {
-    rv = pktns_detect_lost_pkt(pktns, rcs, (uint64_t)conn->largest_ack, ts);
+    rv = ngtcp2_conn_detect_lost_pkt(conn, pktns, rcs,
+                                     (uint64_t)conn->largest_ack, ts);
     if (rv != 0) {
       return rv;
     }
@@ -5894,3 +6031,19 @@ int ngtcp2_conn_set_retry_ocid(ngtcp2_conn *conn, const ngtcp2_cid *ocid) {
 
   return 0;
 }
+
+ngtcp2_strm *ngtcp2_conn_tx_strmq_top(ngtcp2_conn *conn) {
+  assert(!ngtcp2_pq_empty(&conn->tx_strmq));
+  return ngtcp2_struct_of(ngtcp2_pq_top(&conn->tx_strmq), ngtcp2_strm, pe);
+}
+
+void ngtcp2_conn_tx_strmq_pop(ngtcp2_conn *conn) {
+  ngtcp2_strm *strm = ngtcp2_conn_tx_strmq_top(conn);
+  assert(strm);
+  ngtcp2_pq_pop(&conn->tx_strmq);
+  strm->pe.index = NGTCP2_PQ_BAD_INDEX;
+}
+
+int ngtcp2_conn_tx_strmq_push(ngtcp2_conn *conn, ngtcp2_strm *strm) {
+  return ngtcp2_pq_push(&conn->tx_strmq, &strm->pe);
+}
diff --git a/lib/ngtcp2_conn.h b/lib/ngtcp2_conn.h
index d1d49c62..a2d846c8 100644
--- a/lib/ngtcp2_conn.h
+++ b/lib/ngtcp2_conn.h
@@ -41,6 +41,7 @@
 #include "ngtcp2_str.h"
 #include "ngtcp2_pkt.h"
 #include "ngtcp2_log.h"
+#include "ngtcp2_pq.h"
 
 typedef enum {
   /* Client specific handshake states */
@@ -247,7 +248,8 @@ struct ngtcp2_conn {
   ngtcp2_pktns pktns;
   ngtcp2_strm crypto;
   ngtcp2_map strms;
-  ngtcp2_strm *fc_strms;
+  /* tx_strmq contains ngtcp2_strm which has frames to send. */
+  ngtcp2_pq tx_strmq;
   ngtcp2_idtr remote_bidi_idtr;
   ngtcp2_idtr remote_uni_idtr;
   ngtcp2_rcvry_stat rcs;
@@ -423,4 +425,31 @@ void ngtcp2_conn_update_rtt(ngtcp2_conn *conn, uint64_t rtt,
 
 void ngtcp2_conn_set_loss_detection_timer(ngtcp2_conn *conn);
 
+int ngtcp2_conn_detect_lost_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns,
+                                ngtcp2_rcvry_stat *rcs, uint64_t largest_ack,
+                                ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_conn_tx_strmq_top returns the ngtcp2_strm which sits on the
+ * top of queue.  tx_strmq must not be empty.
+ */
+ngtcp2_strm *ngtcp2_conn_tx_strmq_top(ngtcp2_conn *conn);
+
+/*
+ * ngtcp2_conn_tx_strmq_pop pops the ngtcp2_strm from the queue.
+ * tx_strmq must not be empty.
+ */
+void ngtcp2_conn_tx_strmq_pop(ngtcp2_conn *conn);
+
+/*
+ * ngtcp2_conn_tx_strmq_push pushes |strm| into tx_strmq.
+ *
+ *  This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int ngtcp2_conn_tx_strmq_push(ngtcp2_conn *conn, ngtcp2_strm *strm);
+
 #endif /* NGTCP2_CONN_H */
diff --git a/lib/ngtcp2_gaptr.c b/lib/ngtcp2_gaptr.c
index 9278dc2d..0fb1760d 100644
--- a/lib/ngtcp2_gaptr.c
+++ b/lib/ngtcp2_gaptr.c
@@ -58,29 +58,28 @@ void ngtcp2_gaptr_free(ngtcp2_gaptr *gaptr) {
 
 int ngtcp2_gaptr_push(ngtcp2_gaptr *gaptr, uint64_t offset, size_t datalen) {
   int rv;
-  ngtcp2_range m, l, r, q = {offset, offset + datalen};
-  const ngtcp2_range *k;
+  ngtcp2_range k, m, l, r, q = {offset, offset + datalen};
   ngtcp2_psl_it it;
 
   it = ngtcp2_psl_lower_bound(&gaptr->gap, &q);
 
   for (; !ngtcp2_psl_it_end(&it);) {
     k = ngtcp2_psl_it_range(&it);
-    m = ngtcp2_range_intersect(&q, k);
+    m = ngtcp2_range_intersect(&q, &k);
     if (!ngtcp2_range_len(&m)) {
       break;
     }
 
-    if (ngtcp2_range_eq(k, &m)) {
-      rv = ngtcp2_psl_remove(&gaptr->gap, &it, k);
+    if (ngtcp2_range_eq(&k, &m)) {
+      rv = ngtcp2_psl_remove(&gaptr->gap, &it, &k);
       if (rv != 0) {
         return rv;
       }
       continue;
     }
-    ngtcp2_range_cut(&l, &r, k, &m);
+    ngtcp2_range_cut(&l, &r, &k, &m);
     if (ngtcp2_range_len(&l)) {
-      ngtcp2_psl_update_range(&gaptr->gap, k, &l);
+      ngtcp2_psl_update_range(&gaptr->gap, &k, &l);
 
       if (ngtcp2_range_len(&r)) {
         rv = ngtcp2_psl_insert(&gaptr->gap, &it, &r, NULL);
@@ -89,7 +88,7 @@ int ngtcp2_gaptr_push(ngtcp2_gaptr *gaptr, uint64_t offset, size_t datalen) {
         }
       }
     } else if (ngtcp2_range_len(&r)) {
-      ngtcp2_psl_update_range(&gaptr->gap, k, &r);
+      ngtcp2_psl_update_range(&gaptr->gap, &k, &r);
     }
     ngtcp2_psl_it_next(&it);
   }
@@ -98,14 +97,15 @@ int ngtcp2_gaptr_push(ngtcp2_gaptr *gaptr, uint64_t offset, size_t datalen) {
 
 uint64_t ngtcp2_gaptr_first_gap_offset(ngtcp2_gaptr *gaptr) {
   ngtcp2_psl_it it = ngtcp2_psl_begin(&gaptr->gap);
-  const ngtcp2_range *r = ngtcp2_psl_it_range(&it);
-  return r->begin;
+  ngtcp2_range r = ngtcp2_psl_it_range(&it);
+  return r.begin;
 }
 
 int ngtcp2_gaptr_is_pushed(ngtcp2_gaptr *gaptr, uint64_t offset,
                            size_t datalen) {
   ngtcp2_range q = {offset, offset + datalen};
   ngtcp2_psl_it it = ngtcp2_psl_lower_bound(&gaptr->gap, &q);
-  ngtcp2_range m = ngtcp2_range_intersect(&q, ngtcp2_psl_it_range(&it));
+  ngtcp2_range k = ngtcp2_psl_it_range(&it);
+  ngtcp2_range m = ngtcp2_range_intersect(&q, &k);
   return ngtcp2_range_len(&m) == 0;
 }
diff --git a/lib/ngtcp2_log.c b/lib/ngtcp2_log.c
index 62671459..ac4bacdc 100644
--- a/lib/ngtcp2_log.c
+++ b/lib/ngtcp2_log.c
@@ -30,6 +30,7 @@
 #include <errno.h>
 
 #include "ngtcp2_str.h"
+#include "ngtcp2_vec.h"
 
 void ngtcp2_log_init(ngtcp2_log *log, const ngtcp2_cid *scid,
                      ngtcp2_printf log_printf, ngtcp2_tstamp ts,
@@ -198,7 +199,8 @@ static void log_fr_stream(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
       (NGTCP2_LOG_PKT " STREAM(0x%02x) id=0x%" PRIx64 " fin=%d offset=%" PRIu64
                       " len=%" PRIu64 " uni=%d\n"),
       NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type | fr->flags, fr->stream_id,
-      fr->fin, fr->offset, fr->datalen, (fr->stream_id & 0x2) != 0);
+      fr->fin, fr->offset, ngtcp2_vec_len(fr->data, fr->datacnt),
+      (fr->stream_id & 0x2) != 0);
 }
 
 static void log_fr_ack(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
@@ -711,5 +713,5 @@ void ngtcp2_log_info(ngtcp2_log *log, ngtcp2_log_event ev, const char *fmt,
 void ngtcp2_log_tx_cancel(ngtcp2_log *log, const ngtcp2_pkt_hd *hd) {
   ngtcp2_log_info(log, NGTCP2_LOG_EVENT_PKT,
                   "cancel tx pkt %" PRIu64 " type=%s(0x%02x)", hd->pkt_num,
-                  hd->type);
+                  strpkttype(hd), hd->type);
 }
diff --git a/lib/ngtcp2_pkt.c b/lib/ngtcp2_pkt.c
index 21f957cc..a41154e3 100644
--- a/lib/ngtcp2_pkt.c
+++ b/lib/ngtcp2_pkt.c
@@ -462,6 +462,8 @@ ssize_t ngtcp2_pkt_decode_stream_frame(ngtcp2_stream *dest,
     if (payloadlen < len) {
       return NGTCP2_ERR_FRAME_ENCODING;
     }
+  } else {
+    len = payloadlen;
   }
 
   p = payload + 1;
@@ -480,20 +482,23 @@ ssize_t ngtcp2_pkt_decode_stream_frame(ngtcp2_stream *dest,
   }
 
   if (type & NGTCP2_STREAM_LEN_BIT) {
-    dest->datalen = datalen;
     p += ndatalen;
-    dest->data = p;
-    p += dest->datalen;
-
-    assert((size_t)(p - payload) == len);
+  } else {
+    datalen = payloadlen - (size_t)(p - payload);
+  }
 
-    return (ssize_t)len;
+  if (datalen) {
+    dest->data[0].len = datalen;
+    dest->data[0].base = (uint8_t *)p;
+    dest->datacnt = 1;
+    p += datalen;
+  } else {
+    dest->datacnt = 0;
   }
 
-  dest->datalen = payloadlen - (size_t)(p - payload);
-  dest->data = p;
+  assert((size_t)(p - payload) == len);
 
-  return (ssize_t)payloadlen;
+  return (ssize_t)len;
 }
 
 ssize_t ngtcp2_pkt_decode_ack_frame(ngtcp2_ack *dest, const uint8_t *payload,
@@ -1294,6 +1299,8 @@ ssize_t ngtcp2_pkt_encode_stream_frame(uint8_t *out, size_t outlen,
   size_t len = 1;
   uint8_t flags = NGTCP2_STREAM_LEN_BIT;
   uint8_t *p;
+  size_t i;
+  size_t datalen = 0;
 
   if (fr->fin) {
     flags |= NGTCP2_STREAM_FIN_BIT;
@@ -1305,8 +1312,13 @@ ssize_t ngtcp2_pkt_encode_stream_frame(uint8_t *out, size_t outlen,
   }
 
   len += ngtcp2_put_varint_len(fr->stream_id);
-  len += ngtcp2_put_varint_len(fr->datalen);
-  len += fr->datalen;
+
+  for (i = 0; i < fr->datacnt; ++i) {
+    datalen += fr->data[i].len;
+  }
+
+  len += ngtcp2_put_varint_len(datalen);
+  len += datalen;
 
   if (outlen < len) {
     return NGTCP2_ERR_NOBUF;
@@ -1324,10 +1336,12 @@ ssize_t ngtcp2_pkt_encode_stream_frame(uint8_t *out, size_t outlen,
     p = ngtcp2_put_varint(p, fr->offset);
   }
 
-  p = ngtcp2_put_varint(p, fr->datalen);
+  p = ngtcp2_put_varint(p, datalen);
 
-  if (fr->datalen) {
-    p = ngtcp2_cpymem(p, fr->data, fr->datalen);
+  for (i = 0; i < fr->datacnt; ++i) {
+    assert(fr->data[i].len);
+    assert(fr->data[i].base);
+    p = ngtcp2_cpymem(p, fr->data[i].base, fr->data[i].len);
   }
 
   assert((size_t)(p - out) == len);
diff --git a/lib/ngtcp2_pq.c b/lib/ngtcp2_pq.c
index ead72025..176268a3 100644
--- a/lib/ngtcp2_pq.c
+++ b/lib/ngtcp2_pq.c
@@ -87,11 +87,8 @@ int ngtcp2_pq_push(ngtcp2_pq *pq, ngtcp2_pq_entry *item) {
 }
 
 ngtcp2_pq_entry *ngtcp2_pq_top(ngtcp2_pq *pq) {
-  if (pq->length == 0) {
-    return NULL;
-  } else {
-    return pq->q[0];
-  }
+  assert(pq->length);
+  return pq->q[0];
 }
 
 static void bubble_down(ngtcp2_pq *pq, size_t index) {
diff --git a/lib/ngtcp2_pq.h b/lib/ngtcp2_pq.h
index 0625cc1b..982661b1 100644
--- a/lib/ngtcp2_pq.h
+++ b/lib/ngtcp2_pq.h
@@ -36,13 +36,19 @@
 
 /* Implementation of priority queue */
 
-/* "less" function, return nonzero if |lhs| is less than |rhs|. */
-typedef int (*ngtcp2_less)(const void *lhs, const void *rhs);
+/* NGTCP2_PQ_BAD_INDEX is the priority queue index which indicates
+   that an entry is not queued.  Assigning this value to
+   ngtcp2_pq_entry.index can check that the entry is queued or not. */
+#define NGTCP2_PQ_BAD_INDEX SIZE_MAX
 
 typedef struct {
   size_t index;
 } ngtcp2_pq_entry;
 
+/* "less" function, return nonzero if |lhs| is less than |rhs|. */
+typedef int (*ngtcp2_less)(const ngtcp2_pq_entry *lhs,
+                           const ngtcp2_pq_entry *rhs);
+
 typedef struct {
   /* The pointer to the pointer to the item stored */
   ngtcp2_pq_entry **q;
@@ -80,8 +86,8 @@ void ngtcp2_pq_free(ngtcp2_pq *pq);
 int ngtcp2_pq_push(ngtcp2_pq *pq, ngtcp2_pq_entry *item);
 
 /*
- * Returns item at the top of the queue |pq|. If the queue is empty,
- * this function returns NULL.
+ * Returns item at the top of the queue |pq|.  It is undefined if the
+ * queue is empty.
  */
 ngtcp2_pq_entry *ngtcp2_pq_top(ngtcp2_pq *pq);
 
diff --git a/lib/ngtcp2_psl.c b/lib/ngtcp2_psl.c
index 35896d05..a20ecd46 100644
--- a/lib/ngtcp2_psl.c
+++ b/lib/ngtcp2_psl.c
@@ -607,6 +607,6 @@ int ngtcp2_psl_it_end(const ngtcp2_psl_it *it) {
   return ngtcp2_range_eq(&end, &it->blk->nodes[it->i].range);
 }
 
-const ngtcp2_range *ngtcp2_psl_it_range(const ngtcp2_psl_it *it) {
-  return &it->blk->nodes[it->i].range;
+ngtcp2_range ngtcp2_psl_it_range(const ngtcp2_psl_it *it) {
+  return it->blk->nodes[it->i].range;
 }
diff --git a/lib/ngtcp2_psl.h b/lib/ngtcp2_psl.h
index 95352d34..17ae0e0a 100644
--- a/lib/ngtcp2_psl.h
+++ b/lib/ngtcp2_psl.h
@@ -226,6 +226,6 @@ int ngtcp2_psl_it_end(const ngtcp2_psl_it *it);
  * returns nonzero.  In this case, this function returns {UINT64_MAX,
  * UINT64_MAX}.
  */
-const ngtcp2_range *ngtcp2_psl_it_range(const ngtcp2_psl_it *it);
+ngtcp2_range ngtcp2_psl_it_range(const ngtcp2_psl_it *it);
 
 #endif /* NGTCP2_PSL_H */
diff --git a/lib/ngtcp2_rtb.c b/lib/ngtcp2_rtb.c
index 86870010..ae1b1385 100644
--- a/lib/ngtcp2_rtb.c
+++ b/lib/ngtcp2_rtb.c
@@ -30,6 +30,7 @@
 #include "ngtcp2_macro.h"
 #include "ngtcp2_conn.h"
 #include "ngtcp2_log.h"
+#include "ngtcp2_vec.h"
 
 int ngtcp2_frame_chain_new(ngtcp2_frame_chain **pfrc, ngtcp2_mem *mem) {
   *pfrc = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_frame_chain));
@@ -60,6 +61,24 @@ void ngtcp2_frame_chain_del(ngtcp2_frame_chain *frc, ngtcp2_mem *mem) {
 
 void ngtcp2_frame_chain_init(ngtcp2_frame_chain *frc) { frc->next = NULL; }
 
+int ngtcp2_stream_frame_chain_new(ngtcp2_stream_frame_chain **pfrc,
+                                  ngtcp2_mem *mem) {
+  *pfrc = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_stream_frame_chain));
+  if (*pfrc == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  ngtcp2_frame_chain_init(&(*pfrc)->frc);
+  (*pfrc)->pe.index = NGTCP2_PQ_BAD_INDEX;
+
+  return 0;
+}
+
+void ngtcp2_stream_frame_chain_del(ngtcp2_stream_frame_chain *frc,
+                                   ngtcp2_mem *mem) {
+  ngtcp2_mem_free(mem, frc);
+}
+
 ngtcp2_frame_chain *ngtcp2_frame_chain_list_copy(ngtcp2_frame_chain *frc,
                                                  ngtcp2_mem *mem) {
   ngtcp2_frame_chain *nfrc = NULL, **pfrc = &nfrc;
@@ -230,8 +249,9 @@ static int call_acked_stream_offset(ngtcp2_rtb_entry *ent, ngtcp2_conn *conn) {
       }
       prev_stream_offset =
           ngtcp2_gaptr_first_gap_offset(&strm->acked_tx_offset);
-      rv = ngtcp2_gaptr_push(&strm->acked_tx_offset, frc->fr.stream.offset,
-                             frc->fr.stream.datalen);
+      rv = ngtcp2_gaptr_push(
+          &strm->acked_tx_offset, frc->fr.stream.offset,
+          ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt));
       if (rv != 0) {
         return rv;
       }
@@ -313,7 +333,7 @@ static int rtb_on_retransmission_timeout_verified(ngtcp2_rtb *rtb,
 
   for (; !ngtcp2_ksl_it_end(&it);) {
     ent = ngtcp2_ksl_it_get(&it);
-    rv = ngtcp2_ksl_remove(&rtb->ents, &it, ngtcp2_ksl_it_key(&it));
+    rv = ngtcp2_ksl_remove(&rtb->ents, &it, (int64_t)ent->hd.pkt_num);
     if (rv != 0) {
       return rv;
     }
@@ -529,7 +549,7 @@ int ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc,
 
       for (; !ngtcp2_ksl_it_end(&it);) {
         ent = ngtcp2_ksl_it_get(&it);
-        rv = ngtcp2_ksl_remove(&rtb->ents, &it, ngtcp2_ksl_it_key(&it));
+        rv = ngtcp2_ksl_remove(&rtb->ents, &it, (int64_t)ent->hd.pkt_num);
         if (rv != 0) {
           return rv;
         }
@@ -559,7 +579,7 @@ int ngtcp2_rtb_remove_all(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc) {
     ngtcp2_log_pkt_lost(rtb->log, &ent->hd, ent->ts);
 
     rtb_on_remove(rtb, ent);
-    rv = ngtcp2_ksl_remove(&rtb->ents, &it, ngtcp2_ksl_it_key(&it));
+    rv = ngtcp2_ksl_remove(&rtb->ents, &it, (int64_t)ent->hd.pkt_num);
     if (rv != 0) {
       return rv;
     }
diff --git a/lib/ngtcp2_rtb.h b/lib/ngtcp2_rtb.h
index bb5f2740..0a969ade 100644
--- a/lib/ngtcp2_rtb.h
+++ b/lib/ngtcp2_rtb.h
@@ -32,6 +32,7 @@
 #include <ngtcp2/ngtcp2.h>
 
 #include "ngtcp2_ksl.h"
+#include "ngtcp2_pq.h"
 
 struct ngtcp2_cc_stat;
 typedef struct ngtcp2_cc_stat ngtcp2_cc_stat;
@@ -53,6 +54,28 @@ struct ngtcp2_frame_chain {
   ngtcp2_frame fr;
 };
 
+/* NGTCP2_MAX_STREAM_DATACNT is the maximum number of ngtcp2_vec that
+   a ngtcp2_crypto can include. */
+#define NGTCP2_MAX_STREAM_DATACNT 8
+
+struct ngtcp2_stream_frame_chain;
+typedef struct ngtcp2_stream_frame_chain ngtcp2_stream_frame_chain;
+
+/* ngtcp2_stream_frame_chain is an extension to ngtcp2_frame_chain and
+   specific to ngtcp2_stream.  It includes pe in order to push it to
+   ngtcp2_pq. */
+struct ngtcp2_stream_frame_chain {
+  union {
+    ngtcp2_frame_chain frc;
+    struct {
+      ngtcp2_frame_chain *next;
+      ngtcp2_stream fr;
+      ngtcp2_vec extra[NGTCP2_MAX_STREAM_DATACNT - 1];
+    };
+  };
+  ngtcp2_pq_entry pe;
+};
+
 /*
  * ngtcp2_frame_chain_new allocates ngtcp2_frame_chain object and
  * assigns its pointer to |*pfrc|.
@@ -84,6 +107,12 @@ void ngtcp2_frame_chain_del(ngtcp2_frame_chain *frc, ngtcp2_mem *mem);
  */
 void ngtcp2_frame_chain_init(ngtcp2_frame_chain *frc);
 
+int ngtcp2_stream_frame_chain_new(ngtcp2_stream_frame_chain **pfrc,
+                                  ngtcp2_mem *mem);
+
+void ngtcp2_stream_frame_chain_del(ngtcp2_stream_frame_chain *frc,
+                                   ngtcp2_mem *mem);
+
 /*
  * ngtcp2_frame_chain_list_copy creates a copy of |frc| following next
  * field.  It makes copy of each ngtcp2_frame_chain object pointed by
diff --git a/lib/ngtcp2_str.c b/lib/ngtcp2_str.c
index b301ff0f..bfceea90 100644
--- a/lib/ngtcp2_str.c
+++ b/lib/ngtcp2_str.c
@@ -25,6 +25,7 @@
 #include "ngtcp2_str.h"
 
 #include <string.h>
+#include <assert.h>
 
 uint8_t *ngtcp2_cpymem(uint8_t *dest, const uint8_t *src, size_t n) {
   memcpy(dest, src, n);
diff --git a/lib/ngtcp2_strm.c b/lib/ngtcp2_strm.c
index 45ef3e0e..780009c0 100644
--- a/lib/ngtcp2_strm.c
+++ b/lib/ngtcp2_strm.c
@@ -25,12 +25,28 @@
 #include "ngtcp2_strm.h"
 
 #include <string.h>
+#include <assert.h>
+
+#include "ngtcp2_rtb.h"
+#include "ngtcp2_pkt.h"
+#include "ngtcp2_vec.h"
+#include "ngtcp2_macro.h"
+
+static int offset_less(const ngtcp2_pq_entry *lhs, const ngtcp2_pq_entry *rhs) {
+  ngtcp2_stream_frame_chain *lfrc =
+      ngtcp2_struct_of(lhs, ngtcp2_stream_frame_chain, pe);
+  ngtcp2_stream_frame_chain *rfrc =
+      ngtcp2_struct_of(rhs, ngtcp2_stream_frame_chain, pe);
+
+  return lfrc->fr.offset < rfrc->fr.offset;
+}
 
 int ngtcp2_strm_init(ngtcp2_strm *strm, uint64_t stream_id, uint32_t flags,
                      uint64_t max_rx_offset, uint64_t max_tx_offset,
                      void *stream_user_data, ngtcp2_mem *mem) {
   int rv;
 
+  strm->cycle = 0;
   strm->tx_offset = 0;
   strm->last_rx_offset = 0;
   strm->nbuffered = 0;
@@ -41,9 +57,8 @@ int ngtcp2_strm_init(ngtcp2_strm *strm, uint64_t stream_id, uint32_t flags,
   strm->max_tx_offset = max_tx_offset;
   strm->me.key = stream_id;
   strm->me.next = NULL;
+  strm->pe.index = NGTCP2_PQ_BAD_INDEX;
   strm->mem = mem;
-  strm->fc_pprev = NULL;
-  strm->fc_next = NULL;
   /* Initializing to 0 is a bit controversial because application
      error code 0 is STOPPING.  But STOPPING is only sent with
      RST_STREAM in response to STOP_SENDING, and it is not used to
@@ -62,6 +77,8 @@ int ngtcp2_strm_init(ngtcp2_strm *strm, uint64_t stream_id, uint32_t flags,
     goto fail_rob_init;
   }
 
+  ngtcp2_pq_init(&strm->streamfrq, offset_less, mem);
+
   return 0;
 
 fail_rob_init:
@@ -71,10 +88,20 @@ fail_gaptr_init:
 }
 
 void ngtcp2_strm_free(ngtcp2_strm *strm) {
+  ngtcp2_stream_frame_chain *frc;
+
   if (strm == NULL) {
     return;
   }
 
+  for (; !ngtcp2_pq_empty(&strm->streamfrq);) {
+    frc = ngtcp2_struct_of(ngtcp2_pq_top(&strm->streamfrq),
+                           ngtcp2_stream_frame_chain, pe);
+    ngtcp2_pq_pop(&strm->streamfrq);
+    ngtcp2_stream_frame_chain_del(frc, strm->mem);
+  }
+
+  ngtcp2_pq_free(&strm->streamfrq);
   ngtcp2_rob_free(&strm->rob);
   ngtcp2_gaptr_free(&strm->acked_tx_offset);
 }
@@ -91,3 +118,139 @@ int ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data,
 void ngtcp2_strm_shutdown(ngtcp2_strm *strm, uint32_t flags) {
   strm->flags |= flags & NGTCP2_STRM_FLAG_SHUT_RDWR;
 }
+
+int ngtcp2_strm_streamfrq_push(ngtcp2_strm *strm,
+                               ngtcp2_stream_frame_chain *frc) {
+  ngtcp2_stream *fr = &frc->fr;
+
+  assert(fr->type == NGTCP2_FRAME_STREAM);
+  assert(frc->next == NULL);
+
+  return ngtcp2_pq_push(&strm->streamfrq, &frc->pe);
+}
+
+int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm,
+                              ngtcp2_stream_frame_chain **pfrc, size_t left) {
+  ngtcp2_stream *fr, *nfr;
+  ngtcp2_stream_frame_chain *frc, *nfrc;
+  int rv;
+  size_t nmerged;
+  size_t datalen;
+
+  if (ngtcp2_pq_empty(&strm->streamfrq)) {
+    *pfrc = NULL;
+    return 0;
+  }
+
+  frc = ngtcp2_struct_of(ngtcp2_pq_top(&strm->streamfrq),
+                         ngtcp2_stream_frame_chain, pe);
+  ngtcp2_pq_pop(&strm->streamfrq);
+  frc->pe.index = NGTCP2_PQ_BAD_INDEX;
+
+  fr = &frc->fr;
+
+  if (fr->datacnt == NGTCP2_MAX_STREAM_DATACNT) {
+    *pfrc = frc;
+    return 0;
+  }
+
+  datalen = ngtcp2_vec_len(fr->data, fr->datacnt);
+  if (datalen > left) {
+    rv = ngtcp2_stream_frame_chain_new(&nfrc, strm->mem);
+    if (rv != 0) {
+      assert(ngtcp2_err_is_fatal(rv));
+      ngtcp2_stream_frame_chain_del(frc, strm->mem);
+      return rv;
+    }
+
+    nfr = &nfrc->fr;
+    nfr->type = NGTCP2_FRAME_STREAM;
+    nfr->flags = 0;
+    nfr->fin = fr->fin;
+    nfr->stream_id = fr->stream_id;
+    nfr->offset = fr->offset + left;
+    nfr->datacnt = 0;
+
+    ngtcp2_vec_split(fr->data, &fr->datacnt, nfr->data, &nfr->datacnt, left);
+
+    fr->fin = 0;
+
+    rv = ngtcp2_pq_push(&strm->streamfrq, &nfrc->pe);
+    if (rv != 0) {
+      assert(ngtcp2_err_is_fatal(rv));
+      ngtcp2_stream_frame_chain_del(nfrc, strm->mem);
+      ngtcp2_stream_frame_chain_del(frc, strm->mem);
+      return rv;
+    }
+
+    *pfrc = frc;
+
+    return 0;
+  }
+
+  left -= datalen;
+
+  for (; left && fr->datacnt < NGTCP2_MAX_STREAM_DATACNT &&
+         !ngtcp2_pq_empty(&strm->streamfrq);) {
+    nfrc = ngtcp2_struct_of(ngtcp2_pq_top(&strm->streamfrq),
+                            ngtcp2_stream_frame_chain, pe);
+    nfr = &nfrc->fr;
+
+    if (nfr->offset != fr->offset + datalen) {
+      assert(fr->offset + datalen < nfr->offset);
+      break;
+    }
+
+    if (nfr->fin && nfr->datacnt == 0) {
+      frc->fr.fin = 1;
+      ngtcp2_pq_pop(&strm->streamfrq);
+      ngtcp2_stream_frame_chain_del(nfrc, strm->mem);
+      break;
+    }
+
+    nmerged = ngtcp2_vec_merge(fr->data, &fr->datacnt, nfr->data, &nfr->datacnt,
+                               left, NGTCP2_MAX_STREAM_DATACNT);
+    if (nmerged == 0) {
+      break;
+    }
+
+    ngtcp2_pq_pop(&strm->streamfrq);
+
+    datalen += nmerged;
+    nfr->offset += nmerged;
+    left -= nmerged;
+
+    if (nfr->datacnt == 0) {
+      frc->fr.fin = nfrc->fr.fin;
+      ngtcp2_stream_frame_chain_del(nfrc, strm->mem);
+    } else {
+      rv = ngtcp2_pq_push(&strm->streamfrq, &nfrc->pe);
+      if (rv != 0) {
+        ngtcp2_stream_frame_chain_del(nfrc, strm->mem);
+        ngtcp2_stream_frame_chain_del(frc, strm->mem);
+        return rv;
+      }
+    }
+  }
+
+  *pfrc = frc;
+  return 0;
+}
+
+int ngtcp2_strm_streamfrq_empty(ngtcp2_strm *strm) {
+  return ngtcp2_pq_empty(&strm->streamfrq);
+}
+
+void ngtcp2_strm_streamfrq_clear(ngtcp2_strm *strm) {
+  ngtcp2_stream_frame_chain *frc;
+  for (; !ngtcp2_pq_empty(&strm->streamfrq);) {
+    frc = ngtcp2_struct_of(ngtcp2_pq_top(&strm->streamfrq),
+                           ngtcp2_stream_frame_chain, pe);
+    ngtcp2_pq_pop(&strm->streamfrq);
+    ngtcp2_stream_frame_chain_del(frc, strm->mem);
+  }
+}
+
+int ngtcp2_strm_is_tx_queued(ngtcp2_strm *strm) {
+  return strm->pe.index != NGTCP2_PQ_BAD_INDEX;
+}
diff --git a/lib/ngtcp2_strm.h b/lib/ngtcp2_strm.h
index c81ed5d9..946105ef 100644
--- a/lib/ngtcp2_strm.h
+++ b/lib/ngtcp2_strm.h
@@ -35,6 +35,11 @@
 #include "ngtcp2_buf.h"
 #include "ngtcp2_map.h"
 #include "ngtcp2_gaptr.h"
+#include "ngtcp2_ksl.h"
+#include "ngtcp2_pq.h"
+
+struct ngtcp2_stream_frame_chain;
+typedef struct ngtcp2_stream_frame_chain ngtcp2_stream_frame_chain;
 
 typedef enum {
   NGTCP2_STRM_FLAG_NONE = 0,
@@ -60,11 +65,12 @@ typedef enum {
 } ngtcp2_strm_flags;
 
 struct ngtcp2_strm;
-
 typedef struct ngtcp2_strm ngtcp2_strm;
 
 struct ngtcp2_strm {
   ngtcp2_map_entry me;
+  ngtcp2_pq_entry pe;
+  uint64_t cycle;
   uint64_t tx_offset;
   ngtcp2_gaptr acked_tx_offset;
   /* max_tx_offset is the maximum offset that local endpoint can send
@@ -74,6 +80,8 @@ struct ngtcp2_strm {
      this stream. */
   uint64_t last_rx_offset;
   ngtcp2_rob rob;
+  /* streamfrq contains STREAM frame for (re)transmission. */
+  ngtcp2_pq streamfrq;
   /* max_rx_offset is the maximum offset that remote endpoint can send
      to this stream. */
   uint64_t max_rx_offset;
@@ -88,7 +96,6 @@ struct ngtcp2_strm {
   void *stream_user_data;
   /* flags is bit-wise OR of zero or more of ngtcp2_strm_flags. */
   uint32_t flags;
-  ngtcp2_strm **fc_pprev, *fc_next;
   /* app_error_code is an error code the local endpoint sent in
      RST_STREAM or STOP_SENDING. */
   uint16_t app_error_code;
@@ -137,4 +144,36 @@ int ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data,
  */
 void ngtcp2_strm_shutdown(ngtcp2_strm *strm, uint32_t flags);
 
+/*
+ * ngtcp2_strm_streamfrq_push pushes |frc| to streamfrq for
+ * retransmission.
+ */
+int ngtcp2_strm_streamfrq_push(ngtcp2_strm *strm,
+                               ngtcp2_stream_frame_chain *frc);
+
+/*
+ * ngtcp2_strm_streamfrq_pop pops the first ngtcp2_stream_frame_chain
+ * and assigns it to |*pfrc|.  This function splits into or merges
+ * several ngtcp2_stream_frame_chain objects so that the returned
+ * ngtcp2_stream_frame_chain has at most |left| data length.  If there
+ * is no frames to send, this function returns 0 and |*pfrc| is NULL.
+ */
+int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm,
+                              ngtcp2_stream_frame_chain **pfrc, size_t left);
+
+/*
+ * ngtcp2_strm_streamfrq_empty returns nonzero if streamfrq is empty.
+ */
+int ngtcp2_strm_streamfrq_empty(ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_strm_streamfrq_clear removes all frames from streamfrq.
+ */
+void ngtcp2_strm_streamfrq_clear(ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_strm_is_tx_queued returns nonzero if |strm| is queued.
+ */
+int ngtcp2_strm_is_tx_queued(ngtcp2_strm *strm);
+
 #endif /* NGTCP2_STRM_H */
diff --git a/lib/ngtcp2_vec.c b/lib/ngtcp2_vec.c
new file mode 100644
index 00000000..6a2fee33
--- /dev/null
+++ b/lib/ngtcp2_vec.c
@@ -0,0 +1,138 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_vec.h"
+
+#include <string.h>
+#include <assert.h>
+
+size_t ngtcp2_vec_len(const ngtcp2_vec *vec, size_t n) {
+  size_t i;
+  size_t res = 0;
+
+  for (i = 0; i < n; ++i) {
+    res += vec[i].len;
+  }
+
+  return res;
+}
+
+void ngtcp2_vec_split(ngtcp2_vec *src, size_t *psrccnt, ngtcp2_vec *dst,
+                      size_t *pdstcnt, size_t left) {
+  size_t i;
+  size_t srccnt = *psrccnt;
+
+  *pdstcnt = 0;
+
+  for (i = 0; i < srccnt; ++i) {
+    if (left >= src[i].len) {
+      left -= src[i].len;
+      continue;
+    }
+    if (left == 0) {
+      *psrccnt = i;
+      *pdstcnt = srccnt - i;
+      memcpy(dst, src + i, sizeof(ngtcp2_vec) * (*pdstcnt));
+      return;
+    }
+    dst[0].len = src[i].len - left;
+    dst[0].base = src[i].base + left;
+    src[i].len = left;
+    ++i;
+    *psrccnt = i;
+    *pdstcnt = 1 + srccnt - i;
+    memcpy(dst + 1, src + i, sizeof(ngtcp2_vec) * (*pdstcnt - 1));
+
+    return;
+  }
+}
+
+size_t ngtcp2_vec_merge(ngtcp2_vec *dst, size_t *pdstcnt, ngtcp2_vec *src,
+                        size_t *psrccnt, size_t left, size_t maxcnt) {
+  size_t orig_left = left;
+  size_t i;
+  ngtcp2_vec *a, *b;
+
+  assert(maxcnt);
+
+  if (*pdstcnt == 0) {
+    if (*psrccnt == 0) {
+      return 0;
+    }
+
+    a = &dst[0];
+    b = &src[0];
+
+    if (left >= b->len) {
+      *a = *b;
+      ++*pdstcnt;
+      left -= b->len;
+      i = 1;
+    } else {
+      a->len = left;
+      a->base = b->base;
+
+      b->len -= left;
+      b->base += left;
+
+      return left;
+    }
+  } else {
+    i = 0;
+  }
+
+  for (; left && *pdstcnt < maxcnt && i < *psrccnt; ++i) {
+    a = &dst[*pdstcnt - 1];
+    b = &src[i];
+
+    if (left >= b->len) {
+      if (a->base + a->len == b->base) {
+        a->len += b->len;
+      } else {
+        dst[(*pdstcnt)++] = *b;
+      }
+      left -= b->len;
+      continue;
+    }
+
+    if (a->base + a->len == b->base) {
+      a->len += left;
+    } else {
+      dst[*pdstcnt].len = left;
+      dst[*pdstcnt].base = b->base;
+      ++*pdstcnt;
+    }
+
+    b->len -= left;
+    b->base += left;
+    left = 0;
+
+    break;
+  }
+
+  memmove(src, src + i, sizeof(ngtcp2_vec) * (*psrccnt - i));
+  *psrccnt -= i;
+
+  return orig_left - left;
+}
diff --git a/lib/ngtcp2_vec.h b/lib/ngtcp2_vec.h
new file mode 100644
index 00000000..3c52b39c
--- /dev/null
+++ b/lib/ngtcp2_vec.h
@@ -0,0 +1,42 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_VEC_H
+#define NGTCP2_VEC_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <ngtcp2/ngtcp2.h>
+
+size_t ngtcp2_vec_len(const ngtcp2_vec *vec, size_t n);
+
+void ngtcp2_vec_split(ngtcp2_vec *src, size_t *psrccnt, ngtcp2_vec *dst,
+                      size_t *pdstcnt, size_t left);
+
+size_t ngtcp2_vec_merge(ngtcp2_vec *dst, size_t *pdstcnt, ngtcp2_vec *src,
+                        size_t *psrccnt, size_t left, size_t maxcnt);
+
+#endif /* NGTCP2_VEC_H */
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index ddcacddc..6e9ce121 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -47,6 +47,8 @@ if(HAVE_CUNIT)
     ngtcp2_psl_test.c
     ngtcp2_ksl_test.c
     ngtcp2_gaptr_test.c
+    ngtcp2_vec_test.c
+    ngtcp2_strm_test.c
   )
 
   add_executable(main EXCLUDE_FROM_ALL
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 06ea62dc..7d4bf75b 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -42,6 +42,8 @@ OBJECTS = \
 	ngtcp2_psl_test.c \
 	ngtcp2_ksl_test.c \
 	ngtcp2_gaptr_test.c \
+	ngtcp2_vec_test.c \
+	ngtcp2_strm_test.c \
 	ngtcp2_test_helper.c
 HFILES= \
 	ngtcp2_pkt_test.h \
@@ -58,6 +60,8 @@ HFILES= \
 	ngtcp2_psl_test.h \
 	ngtcp2_ksl_test.h \
 	ngtcp2_gaptr_test.h \
+	ngtcp2_vec_test.h \
+	ngtcp2_strm_test.h \
 	ngtcp2_test_helper.h
 
 main_SOURCES = $(HFILES) $(OBJECTS)
diff --git a/tests/main.c b/tests/main.c
index 0810ac78..29398e26 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -45,6 +45,8 @@
 #include "ngtcp2_ksl_test.h"
 #include "ngtcp2_map_test.h"
 #include "ngtcp2_gaptr_test.h"
+#include "ngtcp2_vec_test.h"
+#include "ngtcp2_strm_test.h"
 
 static int init_suite1(void) { return 0; }
 
@@ -216,7 +218,11 @@ int main() {
       !CU_add_test(pSuite, "map_each_free", test_ngtcp2_map_each_free) ||
       !CU_add_test(pSuite, "map_clear", test_ngtcp2_map_clear) ||
       !CU_add_test(pSuite, "gaptr_push", test_ngtcp2_gaptr_push) ||
-      !CU_add_test(pSuite, "gaptr_is_pushed", test_ngtcp2_gaptr_is_pushed)) {
+      !CU_add_test(pSuite, "gaptr_is_pushed", test_ngtcp2_gaptr_is_pushed) ||
+      !CU_add_test(pSuite, "vec_split", test_ngtcp2_vec_split) ||
+      !CU_add_test(pSuite, "vec_merge", test_ngtcp2_vec_merge) ||
+      !CU_add_test(pSuite, "strm_streamfrq_pop",
+                   test_ngtcp2_strm_streamfrq_pop)) {
     CU_cleanup_registry();
     return (int)CU_get_error();
   }
diff --git a/tests/ngtcp2_conn_test.c b/tests/ngtcp2_conn_test.c
index aa403ac6..adbe2e88 100644
--- a/tests/ngtcp2_conn_test.c
+++ b/tests/ngtcp2_conn_test.c
@@ -126,7 +126,7 @@ typedef struct {
      recv_stream_data callback. */
   struct {
     uint64_t stream_id;
-    uint8_t fin;
+    int fin;
     size_t datalen;
   } stream_data;
 } my_user_data;
@@ -227,7 +227,7 @@ static int recv_crypto_data_server(ngtcp2_conn *conn, uint64_t offset,
   return 0;
 }
 
-static int recv_stream_data(ngtcp2_conn *conn, uint64_t stream_id, uint8_t fin,
+static int recv_stream_data(ngtcp2_conn *conn, uint64_t stream_id, int fin,
                             uint64_t offset, const uint8_t *data,
                             size_t datalen, void *user_data,
                             void *stream_user_data) {
@@ -539,8 +539,9 @@ void test_ngtcp2_conn_stream_open_close(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 17;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 17;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid, 1, &fr);
 
@@ -554,7 +555,7 @@ void test_ngtcp2_conn_stream_open_close(void) {
 
   fr.stream.fin = 1;
   fr.stream.offset = 17;
-  fr.stream.datalen = 0;
+  fr.stream.datacnt = 0;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid, 2, &fr);
 
@@ -580,8 +581,9 @@ void test_ngtcp2_conn_stream_open_close(void) {
   fr.stream.stream_id = 2;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 19;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 19;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid, 3, &fr);
 
@@ -592,8 +594,8 @@ void test_ngtcp2_conn_stream_open_close(void) {
   strm = ngtcp2_conn_find_stream(conn, 2);
 
   CU_ASSERT(NGTCP2_STRM_FLAG_SHUT_WR == strm->flags);
-  CU_ASSERT(fr.stream.datalen == strm->last_rx_offset);
-  CU_ASSERT(fr.stream.datalen == ngtcp2_strm_rx_offset(strm));
+  CU_ASSERT(fr.stream.data[0].len == strm->last_rx_offset);
+  CU_ASSERT(fr.stream.data[0].len == ngtcp2_strm_rx_offset(strm));
 
   /* Open a local unidirectional stream */
   rv = ngtcp2_conn_open_uni_stream(conn, &stream_id, NULL);
@@ -629,8 +631,9 @@ void test_ngtcp2_conn_stream_rx_flow_control(void) {
     fr.stream.stream_id = stream_id;
     fr.stream.fin = 0;
     fr.stream.offset = 0;
-    fr.stream.datalen = 1024;
-    fr.stream.data = null_data;
+    fr.stream.datacnt = 1;
+    fr.stream.data[0].len = 1024;
+    fr.stream.data[0].base = null_data;
 
     pktlen =
         write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid, i, &fr);
@@ -643,31 +646,29 @@ void test_ngtcp2_conn_stream_rx_flow_control(void) {
     CU_ASSERT(NULL != strm);
 
     rv = ngtcp2_conn_extend_max_stream_offset(conn, stream_id,
-                                              fr.stream.datalen);
+                                              fr.stream.data[0].len);
 
     CU_ASSERT(0 == rv);
   }
 
-  strm = conn->fc_strms;
+  CU_ASSERT(3 == ngtcp2_pq_size(&conn->tx_strmq));
 
-  CU_ASSERT(8 == strm->stream_id);
+  strm = ngtcp2_conn_find_stream(conn, 0);
 
-  strm = strm->fc_next;
+  CU_ASSERT(ngtcp2_strm_is_tx_queued(strm));
 
-  CU_ASSERT(4 == strm->stream_id);
-
-  strm = strm->fc_next;
+  strm = ngtcp2_conn_find_stream(conn, 4);
 
-  CU_ASSERT(0 == strm->stream_id);
+  CU_ASSERT(ngtcp2_strm_is_tx_queued(strm));
 
-  strm = strm->fc_next;
+  strm = ngtcp2_conn_find_stream(conn, 8);
 
-  CU_ASSERT(NULL == strm);
+  CU_ASSERT(ngtcp2_strm_is_tx_queued(strm));
 
   spktlen = ngtcp2_conn_write_pkt(conn, buf, sizeof(buf), 2);
 
   CU_ASSERT(spktlen > 0);
-  CU_ASSERT(NULL == conn->fc_strms);
+  CU_ASSERT(ngtcp2_pq_empty(&conn->tx_strmq));
 
   for (i = 0; i < 3; ++i) {
     uint64_t stream_id = i * 4;
@@ -695,8 +696,9 @@ void test_ngtcp2_conn_stream_rx_flow_control_error(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 1024;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 1024;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid, 1, &fr);
   rv = ngtcp2_conn_recv(conn, buf, pktlen, 1);
@@ -785,8 +787,9 @@ void test_ngtcp2_conn_rx_flow_control(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 1023;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 1023;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid, 1, &fr);
   rv = ngtcp2_conn_recv(conn, buf, pktlen, 1);
@@ -803,8 +806,9 @@ void test_ngtcp2_conn_rx_flow_control(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 1023;
-  fr.stream.datalen = 1;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 1;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid, 2, &fr);
   rv = ngtcp2_conn_recv(conn, buf, pktlen, 2);
@@ -842,8 +846,9 @@ void test_ngtcp2_conn_rx_flow_control_error(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 1025;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 1025;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid, 1, &fr);
   rv = ngtcp2_conn_recv(conn, buf, pktlen, 1);
@@ -997,8 +1002,9 @@ void test_ngtcp2_conn_recv_rst_stream(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 955;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 955;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid, 1, &fr);
   rv = ngtcp2_conn_recv(conn, buf, pktlen, 1);
@@ -1033,8 +1039,9 @@ void test_ngtcp2_conn_recv_rst_stream(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 955;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 955;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid, 1, &fr);
   rv = ngtcp2_conn_recv(conn, buf, pktlen, 1);
@@ -1067,8 +1074,9 @@ void test_ngtcp2_conn_recv_rst_stream(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 955;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 955;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid, 1, &fr);
   rv = ngtcp2_conn_recv(conn, buf, pktlen, 1);
@@ -1101,8 +1109,9 @@ void test_ngtcp2_conn_recv_rst_stream(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 955;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 955;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid, 1, &fr);
   rv = ngtcp2_conn_recv(conn, buf, pktlen, 1);
@@ -1143,8 +1152,9 @@ void test_ngtcp2_conn_recv_rst_stream(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 955;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 955;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid, 1, &fr);
   rv = ngtcp2_conn_recv(conn, buf, pktlen, 1);
@@ -1172,8 +1182,9 @@ void test_ngtcp2_conn_recv_rst_stream(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 1;
   fr.stream.offset = 0;
-  fr.stream.datalen = 955;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 955;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid, 1, &fr);
   rv = ngtcp2_conn_recv(conn, buf, pktlen, 1);
@@ -1290,8 +1301,9 @@ void test_ngtcp2_conn_recv_rst_stream(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 955;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 955;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid, 1, &fr);
   rv = ngtcp2_conn_recv(conn, buf, pktlen, 1);
@@ -1321,8 +1333,9 @@ void test_ngtcp2_conn_recv_rst_stream(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 955;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 955;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid, 1, &fr);
   rv = ngtcp2_conn_recv(conn, buf, pktlen, 1);
@@ -1511,8 +1524,9 @@ void test_ngtcp2_conn_recv_conn_id_omitted(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 100;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 100;
+  fr.stream.data[0].base = null_data;
 
   /* Receiving packet which has no connection ID while SCID of server
      is not empty. */
@@ -2130,8 +2144,7 @@ void test_ngtcp2_conn_retransmit_protected(void) {
   t += 1000000000;
 
   it = ngtcp2_rtb_head(&conn->pktns.rtb);
-  ngtcp2_rtb_detect_lost_pkt(&conn->pktns.rtb, &conn->pktns.frq, &conn->rcs,
-                             1000000007, 1000000007, ++t);
+  ngtcp2_conn_detect_lost_pkt(conn, &conn->pktns, &conn->rcs, 1000000007, ++t);
   spktlen = ngtcp2_conn_write_pkt(conn, buf, sizeof(buf), ++t);
 
   CU_ASSERT(spktlen > 0);
@@ -2161,8 +2174,7 @@ void test_ngtcp2_conn_retransmit_protected(void) {
   t += 1000000000;
 
   it = ngtcp2_rtb_head(&conn->pktns.rtb);
-  ngtcp2_rtb_detect_lost_pkt(&conn->pktns.rtb, &conn->pktns.frq, &conn->rcs,
-                             1000000007, 1000000007, ++t);
+  ngtcp2_conn_detect_lost_pkt(conn, &conn->pktns, &conn->rcs, 1000000007, ++t);
   spktlen = ngtcp2_conn_write_pkt(conn, buf, (size_t)(spktlen - 1), ++t);
 
   CU_ASSERT(spktlen > 0);
@@ -2194,8 +2206,9 @@ void test_ngtcp2_conn_send_max_stream_data(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = datalen;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = datalen;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid,
                                   ++pkt_num, &fr);
@@ -2210,7 +2223,7 @@ void test_ngtcp2_conn_send_max_stream_data(void) {
 
   strm = ngtcp2_conn_find_stream(conn, 4);
 
-  CU_ASSERT(NULL != strm->fc_pprev);
+  CU_ASSERT(ngtcp2_strm_is_tx_queued(strm));
 
   ngtcp2_conn_del(conn);
 
@@ -2222,8 +2235,9 @@ void test_ngtcp2_conn_send_max_stream_data(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 1;
   fr.stream.offset = 0;
-  fr.stream.datalen = datalen;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = datalen;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid,
                                   ++pkt_num, &fr);
@@ -2238,7 +2252,7 @@ void test_ngtcp2_conn_send_max_stream_data(void) {
 
   strm = ngtcp2_conn_find_stream(conn, 4);
 
-  CU_ASSERT(NULL == strm->fc_pprev);
+  CU_ASSERT(!ngtcp2_strm_is_tx_queued(strm));
 
   ngtcp2_conn_del(conn);
 
@@ -2251,8 +2265,9 @@ void test_ngtcp2_conn_send_max_stream_data(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = datalen;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = datalen;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid,
                                   ++pkt_num, &fr);
@@ -2271,7 +2286,7 @@ void test_ngtcp2_conn_send_max_stream_data(void) {
 
   strm = ngtcp2_conn_find_stream(conn, 4);
 
-  CU_ASSERT(NULL == strm->fc_pprev);
+  CU_ASSERT(!ngtcp2_strm_is_tx_queued(strm));
 
   ngtcp2_conn_del(conn);
 
@@ -2284,8 +2299,9 @@ void test_ngtcp2_conn_send_max_stream_data(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = datalen;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = datalen;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid,
                                   ++pkt_num, &fr);
@@ -2309,7 +2325,7 @@ void test_ngtcp2_conn_send_max_stream_data(void) {
   rv = ngtcp2_conn_extend_max_stream_offset(conn, 4, datalen);
 
   CU_ASSERT(0 == rv);
-  CU_ASSERT(NULL == conn->fc_strms);
+  CU_ASSERT(ngtcp2_pq_empty(&conn->tx_strmq));
 
   ngtcp2_conn_del(conn);
 }
@@ -2334,8 +2350,9 @@ void test_ngtcp2_conn_recv_stream_data(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 111;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 111;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid,
                                   ++pkt_num, &fr);
@@ -2352,8 +2369,9 @@ void test_ngtcp2_conn_recv_stream_data(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 1;
   fr.stream.offset = 111;
-  fr.stream.datalen = 99;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 99;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid,
                                   ++pkt_num, &fr);
@@ -2378,8 +2396,9 @@ void test_ngtcp2_conn_recv_stream_data(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 111;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 111;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid,
                                   ++pkt_num, &fr);
@@ -2396,8 +2415,7 @@ void test_ngtcp2_conn_recv_stream_data(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 1;
   fr.stream.offset = 111;
-  fr.stream.datalen = 0;
-  fr.stream.data = NULL;
+  fr.stream.datacnt = 0;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid,
                                   ++pkt_num, &fr);
@@ -2422,8 +2440,7 @@ void test_ngtcp2_conn_recv_stream_data(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 1;
   fr.stream.offset = 599;
-  fr.stream.datalen = 0;
-  fr.stream.data = NULL;
+  fr.stream.datacnt = 0;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid,
                                   ++pkt_num, &fr);
@@ -2438,8 +2455,9 @@ void test_ngtcp2_conn_recv_stream_data(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 599;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 599;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid,
                                   ++pkt_num, &fr);
@@ -2465,8 +2483,7 @@ void test_ngtcp2_conn_recv_stream_data(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 1;
   fr.stream.offset = 599;
-  fr.stream.datalen = 0;
-  fr.stream.data = NULL;
+  fr.stream.datacnt = 0;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid,
                                   ++pkt_num, &fr);
@@ -2481,8 +2498,9 @@ void test_ngtcp2_conn_recv_stream_data(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 1;
   fr.stream.offset = 0;
-  fr.stream.datalen = 599;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 599;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid,
                                   ++pkt_num, &fr);
@@ -2506,8 +2524,9 @@ void test_ngtcp2_conn_recv_stream_data(void) {
   fr.stream.stream_id = 3;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 911;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 911;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid,
                                   ++pkt_num, &fr);
@@ -2532,8 +2551,9 @@ void test_ngtcp2_conn_recv_stream_data(void) {
   fr.stream.stream_id = 2;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 911;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 911;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid,
                                   ++pkt_num, &fr);
@@ -2557,8 +2577,9 @@ void test_ngtcp2_conn_recv_stream_data(void) {
   fr.stream.stream_id = stream_id;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 9;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 9;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid,
                                   ++pkt_num, &fr);
@@ -2595,8 +2616,7 @@ void test_ngtcp2_conn_recv_stream_data(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 0;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 0;
 
   pktlen = write_single_frame_pkt(conn, buf, sizeof(buf), &conn->scid,
                                   ++pkt_num, &fr);
@@ -2812,8 +2832,9 @@ void test_ngtcp2_conn_recv_early_data(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 1;
   fr.stream.offset = 0;
-  fr.stream.datalen = 911;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 911;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_handshake_pkt(
       conn, buf, sizeof(buf), NGTCP2_PKT_0RTT_PROTECTED, &rcid, &conn->dcid,
@@ -2837,8 +2858,9 @@ void test_ngtcp2_conn_recv_early_data(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 1;
   fr.stream.offset = 0;
-  fr.stream.datalen = 119;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 119;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_handshake_pkt(
       conn, buf, sizeof(buf), NGTCP2_PKT_0RTT_PROTECTED, &rcid, &conn->dcid,
@@ -2886,8 +2908,9 @@ void test_ngtcp2_conn_recv_early_data(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 1;
   fr.stream.offset = 0;
-  fr.stream.datalen = 999;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 999;
+  fr.stream.data[0].base = null_data;
 
   pktlen += write_single_frame_handshake_pkt(
       conn, buf + pktlen, sizeof(buf) - pktlen, NGTCP2_PKT_0RTT_PROTECTED,
@@ -2964,8 +2987,9 @@ void test_ngtcp2_conn_recv_compound_pkt(void) {
   fr.stream.stream_id = 4;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 426;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 426;
+  fr.stream.data[0].base = null_data;
 
   pktlen += write_single_frame_pkt(conn, buf + pktlen, sizeof(buf) - pktlen,
                                    &conn->scid, ++pkt_num, &fr);
@@ -3004,8 +3028,9 @@ void test_ngtcp2_conn_pkt_payloadlen(void) {
   fr.stream.stream_id = 0;
   fr.stream.fin = 0;
   fr.stream.offset = 0;
-  fr.stream.datalen = 131;
-  fr.stream.data = null_data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = 131;
+  fr.stream.data[0].base = null_data;
 
   pktlen = write_single_frame_handshake_pkt(
       conn, buf, sizeof(buf), NGTCP2_PKT_INITIAL, &conn->scid, &conn->dcid,
diff --git a/tests/ngtcp2_gaptr_test.c b/tests/ngtcp2_gaptr_test.c
index 6ceb6d53..a4dadaff 100644
--- a/tests/ngtcp2_gaptr_test.c
+++ b/tests/ngtcp2_gaptr_test.c
@@ -34,43 +34,43 @@ void test_ngtcp2_gaptr_push(void) {
   ngtcp2_gaptr gaptr;
   ngtcp2_mem *mem = ngtcp2_mem_default();
   ngtcp2_psl_it it;
-  const ngtcp2_range *key;
+  ngtcp2_range r;
   int rv;
   size_t i;
 
   ngtcp2_gaptr_init(&gaptr, mem);
 
   it = ngtcp2_psl_begin(&gaptr.gap);
-  key = ngtcp2_psl_it_range(&it);
+  r = ngtcp2_psl_it_range(&it);
 
-  CU_ASSERT(0 == key->begin);
-  CU_ASSERT(UINT64_MAX == key->end);
+  CU_ASSERT(0 == r.begin);
+  CU_ASSERT(UINT64_MAX == r.end);
 
   rv = ngtcp2_gaptr_push(&gaptr, 0, 1);
 
   CU_ASSERT(0 == rv);
 
   it = ngtcp2_psl_begin(&gaptr.gap);
-  key = ngtcp2_psl_it_range(&it);
+  r = ngtcp2_psl_it_range(&it);
 
-  CU_ASSERT(1 == key->begin);
-  CU_ASSERT(UINT64_MAX == key->end);
+  CU_ASSERT(1 == r.begin);
+  CU_ASSERT(UINT64_MAX == r.end);
 
   rv = ngtcp2_gaptr_push(&gaptr, 12389, 133);
 
   CU_ASSERT(0 == rv);
 
   it = ngtcp2_psl_begin(&gaptr.gap);
-  key = ngtcp2_psl_it_range(&it);
+  r = ngtcp2_psl_it_range(&it);
 
-  CU_ASSERT(1 == key->begin);
-  CU_ASSERT(12389 == key->end);
+  CU_ASSERT(1 == r.begin);
+  CU_ASSERT(12389 == r.end);
 
   ngtcp2_psl_it_next(&it);
-  key = ngtcp2_psl_it_range(&it);
+  r = ngtcp2_psl_it_range(&it);
 
-  CU_ASSERT(12389 + 133 == key->begin);
-  CU_ASSERT(UINT64_MAX == key->end);
+  CU_ASSERT(12389 + 133 == r.begin);
+  CU_ASSERT(UINT64_MAX == r.end);
 
   for (i = 0; i < 2; ++i) {
     rv = ngtcp2_gaptr_push(&gaptr, 1, 12389);
@@ -78,10 +78,10 @@ void test_ngtcp2_gaptr_push(void) {
     CU_ASSERT(0 == rv);
 
     it = ngtcp2_psl_begin(&gaptr.gap);
-    key = ngtcp2_psl_it_range(&it);
+    r = ngtcp2_psl_it_range(&it);
 
-    CU_ASSERT(12389 + 133 == key->begin);
-    CU_ASSERT(UINT64_MAX == key->end);
+    CU_ASSERT(12389 + 133 == r.begin);
+    CU_ASSERT(UINT64_MAX == r.end);
   }
 
   rv = ngtcp2_gaptr_push(&gaptr, 12389 + 133 - 1, 2);
@@ -89,10 +89,10 @@ void test_ngtcp2_gaptr_push(void) {
   CU_ASSERT(0 == rv);
 
   it = ngtcp2_psl_begin(&gaptr.gap);
-  key = ngtcp2_psl_it_range(&it);
+  r = ngtcp2_psl_it_range(&it);
 
-  CU_ASSERT(12389 + 133 + 1 == key->begin);
-  CU_ASSERT(UINT64_MAX == key->end);
+  CU_ASSERT(12389 + 133 + 1 == r.begin);
+  CU_ASSERT(UINT64_MAX == r.end);
 
   ngtcp2_gaptr_free(&gaptr);
 }
diff --git a/tests/ngtcp2_idtr_test.c b/tests/ngtcp2_idtr_test.c
index 8e1a795e..646a44c3 100644
--- a/tests/ngtcp2_idtr_test.c
+++ b/tests/ngtcp2_idtr_test.c
@@ -37,7 +37,7 @@ void test_ngtcp2_idtr_open(void) {
   ngtcp2_idtr idtr;
   int rv;
   ngtcp2_psl_it it;
-  const ngtcp2_range *key;
+  ngtcp2_range key;
 
   rv = ngtcp2_idtr_init(&idtr, 0, mem);
 
@@ -50,8 +50,8 @@ void test_ngtcp2_idtr_open(void) {
   it = ngtcp2_psl_begin(&idtr.gap.gap);
   key = ngtcp2_psl_it_range(&it);
 
-  CU_ASSERT(1 == key->begin);
-  CU_ASSERT(UINT64_MAX == key->end);
+  CU_ASSERT(1 == key.begin);
+  CU_ASSERT(UINT64_MAX == key.end);
 
   rv = ngtcp2_idtr_open(&idtr, stream_id_from_id(1000000007));
 
@@ -60,14 +60,14 @@ void test_ngtcp2_idtr_open(void) {
   it = ngtcp2_psl_begin(&idtr.gap.gap);
   key = ngtcp2_psl_it_range(&it);
 
-  CU_ASSERT(1 == key->begin);
-  CU_ASSERT(1000000007 == key->end);
+  CU_ASSERT(1 == key.begin);
+  CU_ASSERT(1000000007 == key.end);
 
   ngtcp2_psl_it_next(&it);
   key = ngtcp2_psl_it_range(&it);
 
-  CU_ASSERT(1000000008 == key->begin);
-  CU_ASSERT(UINT64_MAX == key->end);
+  CU_ASSERT(1000000008 == key.begin);
+  CU_ASSERT(UINT64_MAX == key.end);
 
   rv = ngtcp2_idtr_open(&idtr, stream_id_from_id(0));
 
diff --git a/tests/ngtcp2_pkt_test.c b/tests/ngtcp2_pkt_test.c
index 290a9163..51b3000b 100644
--- a/tests/ngtcp2_pkt_test.c
+++ b/tests/ngtcp2_pkt_test.c
@@ -229,7 +229,8 @@ void test_ngtcp2_pkt_decode_stream_frame(void) {
   CU_ASSERT(0 == fr.stream.fin);
   CU_ASSERT(0xf1f2f3f4u == fr.stream.stream_id);
   CU_ASSERT(0x31f2f3f4f5f6f7f8llu == fr.stream.offset);
-  CU_ASSERT(0x14 == fr.stream.datalen);
+  CU_ASSERT(1 == fr.stream.datacnt);
+  CU_ASSERT(0x14 == fr.stream.data[0].len);
 
   /* Cutting 1 bytes from the tail must cause invalid argument
      error */
@@ -253,7 +254,8 @@ void test_ngtcp2_pkt_decode_stream_frame(void) {
   CU_ASSERT(0 == fr.stream.fin);
   CU_ASSERT(0x31 == fr.stream.stream_id);
   CU_ASSERT(0x00 == fr.stream.offset);
-  CU_ASSERT(0x14 == fr.stream.datalen);
+  CU_ASSERT(1 == fr.stream.datacnt);
+  CU_ASSERT(0x14 == fr.stream.data[0].len);
 
   /* Cutting 1 bytes from the tail must cause invalid argument
      error */
@@ -277,7 +279,8 @@ void test_ngtcp2_pkt_decode_stream_frame(void) {
   CU_ASSERT(1 == fr.stream.fin);
   CU_ASSERT(0x31f2f3f4u == fr.stream.stream_id);
   CU_ASSERT(0x00 == fr.stream.offset);
-  CU_ASSERT(0x14 == fr.stream.datalen);
+  CU_ASSERT(1 == fr.stream.datacnt);
+  CU_ASSERT(0x14 == fr.stream.data[0].len);
 
   memset(&fr, 0, sizeof(fr));
 }
@@ -336,8 +339,9 @@ void test_ngtcp2_pkt_encode_stream_frame(void) {
   fr.stream.fin = 0;
   fr.stream.stream_id = 0xf1f2f3f4u;
   fr.stream.offset = 0x31f2f3f4f5f6f7f8llu;
-  fr.stream.datalen = strsize(data);
-  fr.stream.data = data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = strsize(data);
+  fr.stream.data[0].base = (uint8_t *)data;
 
   framelen = 1 + 8 + 8 + 1 + 17;
 
@@ -354,8 +358,10 @@ void test_ngtcp2_pkt_encode_stream_frame(void) {
   CU_ASSERT(fr.stream.fin == nfr.stream.fin);
   CU_ASSERT(fr.stream.stream_id == nfr.stream.stream_id);
   CU_ASSERT(fr.stream.offset == nfr.stream.offset);
-  CU_ASSERT(fr.stream.datalen == nfr.stream.datalen);
-  CU_ASSERT(0 == memcmp(fr.stream.data, nfr.stream.data, fr.stream.datalen));
+  CU_ASSERT(1 == nfr.stream.datacnt);
+  CU_ASSERT(fr.stream.data[0].len == nfr.stream.data[0].len);
+  CU_ASSERT(0 == memcmp(fr.stream.data[0].base, nfr.stream.data[0].base,
+                        fr.stream.data[0].len));
 
   memset(&nfr, 0, sizeof(nfr));
 
@@ -364,8 +370,9 @@ void test_ngtcp2_pkt_encode_stream_frame(void) {
   fr.stream.fin = 0;
   fr.stream.stream_id = 0x31;
   fr.stream.offset = 0;
-  fr.stream.datalen = strsize(data);
-  fr.stream.data = data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = strsize(data);
+  fr.stream.data[0].base = (uint8_t *)data;
 
   framelen = 1 + 1 + 1 + 17;
 
@@ -381,8 +388,10 @@ void test_ngtcp2_pkt_encode_stream_frame(void) {
   CU_ASSERT(fr.stream.fin == nfr.stream.fin);
   CU_ASSERT(fr.stream.stream_id == nfr.stream.stream_id);
   CU_ASSERT(fr.stream.offset == nfr.stream.offset);
-  CU_ASSERT(fr.stream.datalen == nfr.stream.datalen);
-  CU_ASSERT(0 == memcmp(fr.stream.data, nfr.stream.data, fr.stream.datalen));
+  CU_ASSERT(1 == nfr.stream.datacnt);
+  CU_ASSERT(fr.stream.data[0].len == nfr.stream.data[0].len);
+  CU_ASSERT(0 == memcmp(fr.stream.data[0].base, nfr.stream.data[0].base,
+                        fr.stream.data[0].len));
 
   memset(&nfr, 0, sizeof(nfr));
 
@@ -391,8 +400,9 @@ void test_ngtcp2_pkt_encode_stream_frame(void) {
   fr.stream.fin = 1;
   fr.stream.stream_id = 0xf1f2f3f4u;
   fr.stream.offset = 0x31f2f3f4f5f6f7f8llu;
-  fr.stream.datalen = strsize(data);
-  fr.stream.data = data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = strsize(data);
+  fr.stream.data[0].base = (uint8_t *)data;
 
   framelen = 1 + 8 + 8 + 1 + 17;
 
@@ -409,8 +419,10 @@ void test_ngtcp2_pkt_encode_stream_frame(void) {
   CU_ASSERT(fr.stream.fin == nfr.stream.fin);
   CU_ASSERT(fr.stream.stream_id == nfr.stream.stream_id);
   CU_ASSERT(fr.stream.offset == nfr.stream.offset);
-  CU_ASSERT(fr.stream.datalen == nfr.stream.datalen);
-  CU_ASSERT(0 == memcmp(fr.stream.data, nfr.stream.data, fr.stream.datalen));
+  CU_ASSERT(1 == nfr.stream.datacnt);
+  CU_ASSERT(fr.stream.data[0].len == nfr.stream.data[0].len);
+  CU_ASSERT(0 == memcmp(fr.stream.data[0].base, nfr.stream.data[0].base,
+                        fr.stream.data[0].len));
 
   /* Make sure that we check the length properly. */
   for (i = 1; i < framelen; ++i) {
@@ -426,8 +438,9 @@ void test_ngtcp2_pkt_encode_stream_frame(void) {
   fr.stream.fin = 1;
   fr.stream.stream_id = 0xf1f2f3f4u;
   fr.stream.offset = 0x31f2f3f4f5f6f7f8llu;
-  fr.stream.datalen = strsize(data);
-  fr.stream.data = data;
+  fr.stream.datacnt = 1;
+  fr.stream.data[0].len = strsize(data);
+  fr.stream.data[0].base = (uint8_t *)data;
 
   framelen = 1 + 8 + 8 + 1 + 17;
 
diff --git a/tests/ngtcp2_psl_test.c b/tests/ngtcp2_psl_test.c
index 93d277a9..862960ce 100644
--- a/tests/ngtcp2_psl_test.c
+++ b/tests/ngtcp2_psl_test.c
@@ -37,7 +37,6 @@ void test_ngtcp2_psl_insert(void) {
   ngtcp2_psl psl;
   ngtcp2_mem *mem = ngtcp2_mem_default();
   size_t i;
-  const ngtcp2_range *pr;
   ngtcp2_range r;
   ngtcp2_psl_it it;
 
@@ -46,16 +45,16 @@ void test_ngtcp2_psl_insert(void) {
   for (i = 0; i < arraylen(keys); ++i) {
     ngtcp2_psl_insert(&psl, NULL, &keys[i], NULL);
     it = ngtcp2_psl_lower_bound(&psl, &keys[i]);
-
-    CU_ASSERT(ngtcp2_range_eq(&keys[i], ngtcp2_psl_it_range(&it)));
+    r = ngtcp2_psl_it_range(&it);
+    CU_ASSERT(ngtcp2_range_eq(&keys[i], &r));
   }
 
   for (i = 0; i < arraylen(keys); ++i) {
     ngtcp2_psl_remove(&psl, NULL, &keys[i]);
     it = ngtcp2_psl_lower_bound(&psl, &keys[i]);
-    pr = ngtcp2_psl_it_range(&it);
+    r = ngtcp2_psl_it_range(&it);
 
-    CU_ASSERT(keys[i].end <= pr->begin);
+    CU_ASSERT(keys[i].end <= r.begin);
   }
 
   ngtcp2_psl_free(&psl);
@@ -71,21 +70,21 @@ void test_ngtcp2_psl_insert(void) {
   /* Removing [7, 8) requires relocation */
   ngtcp2_range_init(&r, 7, 8);
   ngtcp2_psl_remove(&psl, &it, &r);
-  pr = ngtcp2_psl_it_range(&it);
+  r = ngtcp2_psl_it_range(&it);
 
-  CU_ASSERT(8 == pr->begin);
-  CU_ASSERT(9 == pr->end);
+  CU_ASSERT(8 == r.begin);
+  CU_ASSERT(9 == r.end);
 
   it = ngtcp2_psl_lower_bound(&psl, &r);
-  pr = ngtcp2_psl_it_range(&it);
+  r = ngtcp2_psl_it_range(&it);
 
-  CU_ASSERT(8 == pr->begin);
-  CU_ASSERT(9 == pr->end);
+  CU_ASSERT(8 == r.begin);
+  CU_ASSERT(9 == r.end);
 
-  pr = &psl.head->nodes[0].range;
+  r = psl.head->nodes[0].range;
 
-  CU_ASSERT(8 == pr->begin);
-  CU_ASSERT(9 == pr->end);
+  CU_ASSERT(8 == r.begin);
+  CU_ASSERT(9 == r.end);
 
   ngtcp2_psl_free(&psl);
 
@@ -100,16 +99,16 @@ void test_ngtcp2_psl_insert(void) {
 
   ngtcp2_range_init(&r, 63, 64);
   ngtcp2_psl_remove(&psl, &it, &r);
-  pr = ngtcp2_psl_it_range(&it);
+  r = ngtcp2_psl_it_range(&it);
 
-  CU_ASSERT(64 == pr->begin);
-  CU_ASSERT(65 == pr->end);
+  CU_ASSERT(64 == r.begin);
+  CU_ASSERT(65 == r.end);
 
   it = ngtcp2_psl_lower_bound(&psl, &r);
-  pr = ngtcp2_psl_it_range(&it);
+  r = ngtcp2_psl_it_range(&it);
 
-  CU_ASSERT(64 == pr->begin);
-  CU_ASSERT(65 == pr->end);
+  CU_ASSERT(64 == r.begin);
+  CU_ASSERT(65 == r.end);
 
   ngtcp2_psl_free(&psl);
 
diff --git a/tests/ngtcp2_strm_test.c b/tests/ngtcp2_strm_test.c
new file mode 100644
index 00000000..e3b913e9
--- /dev/null
+++ b/tests/ngtcp2_strm_test.c
@@ -0,0 +1,253 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_strm_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include "ngtcp2_strm.h"
+#include "ngtcp2_test_helper.h"
+
+static uint8_t nulldata[1024];
+
+static void setup_strm_streamfrq_fixture(ngtcp2_strm *strm, ngtcp2_mem *mem) {
+  ngtcp2_stream_frame_chain *frc;
+  ngtcp2_vec *data;
+
+  ngtcp2_strm_init(strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, mem);
+
+  ngtcp2_stream_frame_chain_new(&frc, mem);
+  frc->fr.type = NGTCP2_FRAME_STREAM;
+  frc->fr.fin = 0;
+  frc->fr.offset = 0;
+  frc->fr.datacnt = 2;
+  data = frc->fr.data;
+  data[0].len = 11;
+  data[0].base = nulldata;
+  data[1].len = 19;
+  data[1].base = nulldata + 11;
+
+  ngtcp2_strm_streamfrq_push(strm, frc);
+
+  ngtcp2_stream_frame_chain_new(&frc, mem);
+  frc->fr.type = NGTCP2_FRAME_STREAM;
+  frc->fr.fin = 0;
+  frc->fr.offset = 30;
+  frc->fr.datacnt = 2;
+  data = frc->fr.data;
+  data[0].len = 17;
+  data[0].base = nulldata + 30;
+  data[1].len = 29;
+  data[1].base = nulldata + 30 + 17;
+
+  ngtcp2_strm_streamfrq_push(strm, frc);
+
+  ngtcp2_stream_frame_chain_new(&frc, mem);
+  frc->fr.type = NGTCP2_FRAME_STREAM;
+  frc->fr.fin = 0;
+  frc->fr.offset = 76;
+  frc->fr.datacnt = 2;
+  data = frc->fr.data;
+  data[0].len = 31;
+  data[0].base = nulldata + 256;
+  data[1].len = 1;
+  data[1].base = nulldata + 512;
+
+  ngtcp2_strm_streamfrq_push(strm, frc);
+}
+
+void test_ngtcp2_strm_streamfrq_pop(void) {
+  ngtcp2_strm strm;
+  ngtcp2_stream_frame_chain *frc;
+  ngtcp2_mem *mem = ngtcp2_mem_default();
+  int rv;
+  ngtcp2_vec *data;
+
+  /* Get first chain */
+  setup_strm_streamfrq_fixture(&strm, mem);
+
+  frc = NULL;
+  rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 30);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(2 == frc->fr.datacnt);
+
+  data = frc->fr.data;
+
+  CU_ASSERT(11 == data[0].len);
+  CU_ASSERT(19 == data[1].len);
+  CU_ASSERT(2 == ngtcp2_pq_size(&strm.streamfrq));
+
+  ngtcp2_stream_frame_chain_del(frc, mem);
+  ngtcp2_strm_free(&strm);
+
+  /* Get merged chain */
+  setup_strm_streamfrq_fixture(&strm, mem);
+
+  frc = NULL;
+  rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 76);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(2 == frc->fr.datacnt);
+
+  data = frc->fr.data;
+
+  CU_ASSERT(11 == data[0].len);
+  CU_ASSERT(19 + 46 == data[1].len);
+  CU_ASSERT(1 == ngtcp2_pq_size(&strm.streamfrq));
+
+  ngtcp2_stream_frame_chain_del(frc, mem);
+  ngtcp2_strm_free(&strm);
+
+  /* Get merged chain partially */
+  setup_strm_streamfrq_fixture(&strm, mem);
+
+  frc = NULL;
+  rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 75);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(2 == frc->fr.datacnt);
+
+  data = frc->fr.data;
+
+  CU_ASSERT(11 == data[0].len);
+  CU_ASSERT(19 + 45 == data[1].len);
+  CU_ASSERT(2 == ngtcp2_pq_size(&strm.streamfrq));
+
+  ngtcp2_stream_frame_chain_del(frc, mem);
+
+  frc = NULL;
+  rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 1);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(75 == frc->fr.offset);
+  CU_ASSERT(1 == frc->fr.datacnt);
+  CU_ASSERT(1 == frc->fr.data[0].len);
+  CU_ASSERT(nulldata + 30 + 17 + 28 == frc->fr.data[0].base);
+
+  ngtcp2_stream_frame_chain_del(frc, mem);
+  ngtcp2_strm_free(&strm);
+
+  /* Not continuous merge */
+  setup_strm_streamfrq_fixture(&strm, mem);
+
+  frc = NULL;
+  rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 77);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(3 == frc->fr.datacnt);
+
+  data = frc->fr.data;
+
+  CU_ASSERT(11 == data[0].len);
+  CU_ASSERT(19 + 46 == data[1].len);
+  CU_ASSERT(1 == data[2].len);
+  CU_ASSERT(nulldata + 256 == data[2].base);
+  CU_ASSERT(1 == ngtcp2_pq_size(&strm.streamfrq));
+
+  ngtcp2_stream_frame_chain_del(frc, mem);
+
+  frc = NULL;
+  rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 1024);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(77 == frc->fr.offset);
+  CU_ASSERT(2 == frc->fr.datacnt);
+
+  data = frc->fr.data;
+
+  CU_ASSERT(30 == data[0].len);
+  CU_ASSERT(nulldata + 256 + 1 == data[0].base);
+
+  ngtcp2_stream_frame_chain_del(frc, mem);
+  ngtcp2_strm_free(&strm);
+
+  /* offset gap */
+  ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, mem);
+
+  ngtcp2_stream_frame_chain_new(&frc, mem);
+  frc->fr.type = NGTCP2_FRAME_STREAM;
+  frc->fr.fin = 0;
+  frc->fr.offset = 0;
+  frc->fr.datacnt = 1;
+  data = frc->fr.data;
+  data[0].len = 11;
+  data[0].base = nulldata;
+
+  ngtcp2_strm_streamfrq_push(&strm, frc);
+
+  ngtcp2_stream_frame_chain_new(&frc, mem);
+  frc->fr.type = NGTCP2_FRAME_STREAM;
+  frc->fr.fin = 0;
+  frc->fr.offset = 30;
+  frc->fr.datacnt = 1;
+  data = frc->fr.data;
+  data[0].len = 17;
+  data[0].base = nulldata + 30;
+
+  ngtcp2_strm_streamfrq_push(&strm, frc);
+
+  frc = NULL;
+  rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 1024);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(1 == frc->fr.datacnt);
+  CU_ASSERT(11 == frc->fr.data[0].len);
+  CU_ASSERT(1 == ngtcp2_pq_size(&strm.streamfrq));
+
+  ngtcp2_stream_frame_chain_del(frc, mem);
+  ngtcp2_strm_free(&strm);
+
+  /* fin */
+  ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, mem);
+
+  ngtcp2_stream_frame_chain_new(&frc, mem);
+  frc->fr.type = NGTCP2_FRAME_STREAM;
+  frc->fr.fin = 0;
+  frc->fr.offset = 0;
+  frc->fr.datacnt = 1;
+  data = frc->fr.data;
+  data[0].len = 11;
+  data[0].base = nulldata;
+
+  ngtcp2_strm_streamfrq_push(&strm, frc);
+
+  ngtcp2_stream_frame_chain_new(&frc, mem);
+  frc->fr.type = NGTCP2_FRAME_STREAM;
+  frc->fr.fin = 1;
+  frc->fr.offset = 11;
+  frc->fr.datacnt = 0;
+
+  ngtcp2_strm_streamfrq_push(&strm, frc);
+
+  frc = NULL;
+  rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 1024);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(1 == frc->fr.fin);
+  CU_ASSERT(1 == frc->fr.datacnt);
+
+  ngtcp2_stream_frame_chain_del(frc, mem);
+  ngtcp2_strm_free(&strm);
+}
diff --git a/tests/ngtcp2_strm_test.h b/tests/ngtcp2_strm_test.h
new file mode 100644
index 00000000..687e265f
--- /dev/null
+++ b/tests/ngtcp2_strm_test.h
@@ -0,0 +1,34 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_STRM_TEST_H
+#define NGTCP2_STRM_TEST_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_ngtcp2_strm_streamfrq_pop(void);
+
+#endif /* NGTCP2_STRM_TEST_H */
diff --git a/tests/ngtcp2_vec_test.c b/tests/ngtcp2_vec_test.c
new file mode 100644
index 00000000..9ca07604
--- /dev/null
+++ b/tests/ngtcp2_vec_test.c
@@ -0,0 +1,224 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_vec_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include "ngtcp2_vec.h"
+#include "ngtcp2_test_helper.h"
+
+void test_ngtcp2_vec_split(void) {
+  uint8_t nulldata[1024];
+  ngtcp2_vec a[16], b[16];
+  size_t acnt, bcnt;
+
+  /* No split occurs */
+  acnt = 1;
+  a[0].len = 135;
+  a[0].base = nulldata;
+
+  bcnt = 0;
+  b[0].len = 0;
+  b[0].base = NULL;
+
+  ngtcp2_vec_split(a, &acnt, b, &bcnt, 135);
+
+  CU_ASSERT(1 == acnt);
+  CU_ASSERT(135 == a[0].len);
+  CU_ASSERT(nulldata == a[0].base);
+  CU_ASSERT(0 == bcnt);
+  CU_ASSERT(0 == b[0].len);
+  CU_ASSERT(NULL == b[0].base);
+
+  /* Split once */
+  acnt = 1;
+  a[0].len = 135;
+  a[0].base = nulldata;
+
+  bcnt = 0;
+  b[0].len = 0;
+  b[0].base = NULL;
+
+  ngtcp2_vec_split(a, &acnt, b, &bcnt, 87);
+
+  CU_ASSERT(1 == acnt);
+  CU_ASSERT(87 == a[0].len);
+  CU_ASSERT(nulldata == a[0].base);
+  CU_ASSERT(1 == bcnt);
+  CU_ASSERT(48 == b[0].len);
+  CU_ASSERT(nulldata + 87 == b[0].base);
+
+  /* Multiple a vector; split at ngtcp2_vec boundary */
+  acnt = 2;
+  a[0].len = 33;
+  a[0].base = nulldata;
+  a[1].len = 89;
+  a[1].base = nulldata + 33;
+
+  bcnt = 0;
+  b[0].len = 0;
+  b[0].base = NULL;
+
+  ngtcp2_vec_split(a, &acnt, b, &bcnt, 33);
+
+  CU_ASSERT(1 == acnt);
+  CU_ASSERT(33 == a[0].len);
+  CU_ASSERT(nulldata == a[0].base);
+  CU_ASSERT(1 == bcnt);
+  CU_ASSERT(89 == b[0].len);
+  CU_ASSERT(nulldata + 33 == b[0].base);
+
+  /* Multiple a vector; not split at ngtcp2_vec boundary */
+  acnt = 3;
+  a[0].len = 33;
+  a[0].base = nulldata;
+  a[1].len = 89;
+  a[1].base = nulldata + 33;
+  a[2].len = 211;
+  a[2].base = nulldata + 33 + 89;
+
+  bcnt = 0;
+  b[0].len = 0;
+  b[0].base = NULL;
+
+  ngtcp2_vec_split(a, &acnt, b, &bcnt, 34);
+
+  CU_ASSERT(2 == acnt);
+  CU_ASSERT(33 == a[0].len);
+  CU_ASSERT(nulldata == a[0].base);
+  CU_ASSERT(1 == a[1].len);
+  CU_ASSERT(nulldata + 33 == a[1].base);
+  CU_ASSERT(2 == bcnt);
+  CU_ASSERT(88 == b[0].len);
+  CU_ASSERT(nulldata + 34 == b[0].base);
+  CU_ASSERT(211 == b[1].len);
+  CU_ASSERT(nulldata + 34 + 88 == b[1].base);
+}
+
+void test_ngtcp2_vec_merge(void) {
+  uint8_t nulldata[1024];
+  ngtcp2_vec a[16], b[16];
+  size_t acnt, bcnt;
+  size_t nmerged;
+
+  /* Merge one ngtcp2_vec completely */
+  acnt = 1;
+  a[0].len = 33;
+  a[0].base = nulldata;
+
+  bcnt = 1;
+  b[0].len = 11;
+  b[0].base = nulldata + 33;
+
+  nmerged = ngtcp2_vec_merge(a, &acnt, b, &bcnt, 11, 16);
+
+  CU_ASSERT(11 == nmerged);
+  CU_ASSERT(1 == acnt);
+  CU_ASSERT(44 == a[0].len);
+  CU_ASSERT(nulldata == a[0].base);
+  CU_ASSERT(0 == bcnt);
+
+  /* Merge ngtcp2_vec partially */
+  acnt = 1;
+  a[0].len = 33;
+  a[0].base = nulldata;
+
+  bcnt = 1;
+  b[0].len = 11;
+  b[0].base = nulldata + 33;
+
+  nmerged = ngtcp2_vec_merge(a, &acnt, b, &bcnt, 10, 16);
+
+  CU_ASSERT(10 == nmerged);
+  CU_ASSERT(1 == acnt);
+  CU_ASSERT(43 == a[0].len);
+  CU_ASSERT(nulldata == a[0].base);
+  CU_ASSERT(1 == bcnt);
+  CU_ASSERT(1 == b[0].len);
+  CU_ASSERT(nulldata + 33 + 10 == b[0].base);
+
+  /* Merge one ngtcp2_vec completely; data is not continuous */
+  acnt = 1;
+  a[0].len = 33;
+  a[0].base = nulldata;
+
+  bcnt = 1;
+  b[0].len = 11;
+  b[0].base = nulldata + 256;
+
+  nmerged = ngtcp2_vec_merge(a, &acnt, b, &bcnt, 11, 16);
+
+  CU_ASSERT(11 == nmerged);
+  CU_ASSERT(2 == acnt);
+  CU_ASSERT(33 == a[0].len);
+  CU_ASSERT(nulldata == a[0].base);
+  CU_ASSERT(11 == a[1].len);
+  CU_ASSERT(nulldata + 256 == a[1].base);
+  CU_ASSERT(0 == bcnt);
+
+  /* Merge ngtcp2_vec partially; data is not continuous */
+  acnt = 1;
+  a[0].len = 33;
+  a[0].base = nulldata;
+
+  bcnt = 1;
+  b[0].len = 11;
+  b[0].base = nulldata + 256;
+
+  nmerged = ngtcp2_vec_merge(a, &acnt, b, &bcnt, 10, 16);
+
+  CU_ASSERT(10 == nmerged);
+  CU_ASSERT(2 == acnt);
+  CU_ASSERT(33 == a[0].len);
+  CU_ASSERT(nulldata == a[0].base);
+  CU_ASSERT(10 == a[1].len);
+  CU_ASSERT(nulldata + 256 == a[1].base);
+  CU_ASSERT(1 == bcnt);
+  CU_ASSERT(1 == b[0].len);
+  CU_ASSERT(nulldata + 256 + 10 == b[0].base);
+
+  /* Merge ends at the ngtcp2_vec boundary */
+  acnt = 1;
+  a[0].len = 33;
+  a[0].base = nulldata;
+
+  bcnt = 2;
+  b[0].len = 11;
+  b[0].base = nulldata + 256;
+  b[1].len = 19;
+  b[1].base = nulldata + 256 + 11;
+
+  nmerged = ngtcp2_vec_merge(a, &acnt, b, &bcnt, 11, 16);
+
+  CU_ASSERT(11 == nmerged);
+  CU_ASSERT(2 == acnt);
+  CU_ASSERT(33 == a[0].len);
+  CU_ASSERT(nulldata == a[0].base);
+  CU_ASSERT(11 == a[1].len);
+  CU_ASSERT(nulldata + 256 == a[1].base);
+  CU_ASSERT(1 == bcnt);
+  CU_ASSERT(19 == b[0].len);
+  CU_ASSERT(nulldata + 256 + 11 == b[0].base);
+}
diff --git a/tests/ngtcp2_vec_test.h b/tests/ngtcp2_vec_test.h
new file mode 100644
index 00000000..c8607788
--- /dev/null
+++ b/tests/ngtcp2_vec_test.h
@@ -0,0 +1,35 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_VEC_TEST_H
+#define NGTCP2_VEC_TEST_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_ngtcp2_vec_split(void);
+void test_ngtcp2_vec_merge(void);
+
+#endif /* NGTCP2_VEC_TEST_H */
-- 
GitLab