From f9eed6c05ec6ff4f7540e9df7beb62d53ca90664 Mon Sep 17 00:00:00 2001
From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
Date: Sat, 28 Apr 2018 18:06:13 +0900
Subject: [PATCH] Improve perf when getting scattered stream data in certain
 pattern

---
 lib/CMakeLists.txt      |   1 +
 lib/Makefile.am         |   4 +-
 lib/ngtcp2_psl.c        | 494 ++++++++++++++++++++++++++++++++++++++++
 lib/ngtcp2_psl.h        | 217 ++++++++++++++++++
 lib/ngtcp2_rob.c        | 272 +++++++++++++---------
 lib/ngtcp2_rob.h        |  20 +-
 tests/CMakeLists.txt    |   1 +
 tests/Makefile.am       |   2 +
 tests/main.c            |   3 +
 tests/ngtcp2_psl_test.c | 131 +++++++++++
 tests/ngtcp2_psl_test.h |  34 +++
 tests/ngtcp2_rob_test.c | 226 ++++++++++++++++--
 tests/ngtcp2_rob_test.h |   1 +
 13 files changed, 1256 insertions(+), 150 deletions(-)
 create mode 100644 lib/ngtcp2_psl.c
 create mode 100644 lib/ngtcp2_psl.h
 create mode 100644 tests/ngtcp2_psl_test.c
 create mode 100644 tests/ngtcp2_psl_test.h

diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 8aa2fe0a..275d02c9 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -53,6 +53,7 @@ set(ngtcp2_SOURCES
   ngtcp2_ringbuf.c
   ngtcp2_log.c
   ngtcp2_cid.c
+  ngtcp2_psl.c
 )
 
 # Public shared library
diff --git a/lib/Makefile.am b/lib/Makefile.am
index ea74ec49..02da35ca 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -52,7 +52,8 @@ OBJECTS = \
 	ngtcp2_gaptr.c \
 	ngtcp2_ringbuf.c \
 	ngtcp2_log.c \
-	ngtcp2_cid.c
+	ngtcp2_cid.c \
+	ngtcp2_psl.c
 
 HFILES = \
 	ngtcp2_pkt.h \
@@ -76,6 +77,7 @@ HFILES = \
 	ngtcp2_ringbuf.h \
 	ngtcp2_log.h \
 	ngtcp2_cid.h \
+	ngtcp2_psl.h \
 	ngtcp2_macro.h
 
 libngtcp2_la_SOURCES = $(HFILES) $(OBJECTS)
diff --git a/lib/ngtcp2_psl.c b/lib/ngtcp2_psl.c
new file mode 100644
index 00000000..7414df5c
--- /dev/null
+++ b/lib/ngtcp2_psl.c
@@ -0,0 +1,494 @@
+/*
+ * 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_psl.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+
+#include "ngtcp2_macro.h"
+#include "ngtcp2_mem.h"
+
+int ngtcp2_psl_init(ngtcp2_psl *psl, ngtcp2_mem *mem) {
+  ngtcp2_psl_blk *head;
+
+  psl->mem = mem;
+  psl->head = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_psl_blk));
+  if (!psl->head) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  head = psl->head;
+
+  head->next = NULL;
+  head->n = 1;
+  head->leaf = 1;
+  head->nodes[0].range.begin = UINT64_MAX;
+  head->nodes[0].range.end = UINT64_MAX;
+  head->nodes[0].data = NULL;
+
+  return 0;
+}
+
+/*
+ * free_blk frees |blk| recursively.
+ */
+static void free_blk(ngtcp2_psl_blk *blk, ngtcp2_mem *mem) {
+  size_t i;
+
+  if (!blk->leaf) {
+    for (i = 0; i < blk->n; ++i) {
+      free_blk(blk->nodes[i].blk, mem);
+    }
+  }
+
+  ngtcp2_mem_free(mem, blk);
+}
+
+void ngtcp2_psl_free(ngtcp2_psl *psl) {
+  if (!psl) {
+    return;
+  }
+
+  free_blk(psl->head, psl->mem);
+}
+
+/*
+ * psl_split_blk splits |blk| into 2 ngtcp2_psl_blk objects.  The new
+ * ngtcp2_psl_blk is always the "right" block.
+ *
+ * It returns the pointer to the ngtcp2_psl_blk created which is the
+ * located at the right of |blk|, or NULL which indicates out of
+ * memory error.
+ */
+static ngtcp2_psl_blk *psl_split_blk(ngtcp2_psl *psl, ngtcp2_psl_blk *blk) {
+  ngtcp2_psl_blk *rblk;
+
+  rblk = ngtcp2_mem_malloc(psl->mem, sizeof(ngtcp2_psl_blk));
+  if (rblk == NULL) {
+    return NULL;
+  }
+
+  rblk->next = blk->next;
+  blk->next = rblk;
+  rblk->leaf = blk->leaf;
+
+  rblk->n = blk->n / 2;
+
+  memcpy(rblk->nodes, &blk->nodes[blk->n - rblk->n],
+         sizeof(ngtcp2_psl_node) * rblk->n);
+
+  blk->n -= rblk->n;
+
+  return rblk;
+}
+
+/*
+ * psl_split_node splits a node included in |blk| at the position |i|
+ * into 2 adjacent nodes.  The new node is always inserted at the
+ * position |i+1|.
+ *
+ * It returns 0 if it succeeds, or one of the following negative error
+ * codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *   Out of memory.
+ */
+static int psl_split_node(ngtcp2_psl *psl, ngtcp2_psl_blk *blk, size_t i) {
+  ngtcp2_psl_blk *lblk = blk->nodes[i].blk, *rblk;
+
+  assert(blk->n < NGTCP2_PSL_NBLK);
+
+  rblk = psl_split_blk(psl, lblk);
+  if (rblk == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  memmove(&blk->nodes[i + 2], &blk->nodes[i + 1],
+          sizeof(ngtcp2_psl_node) * (blk->n - (i + 1)));
+
+  blk->nodes[i + 1].blk = rblk;
+
+  ++blk->n;
+
+  blk->nodes[i].range = lblk->nodes[lblk->n - 1].range;
+  blk->nodes[i + 1].range = rblk->nodes[rblk->n - 1].range;
+
+  return 0;
+}
+
+/*
+ * psl_split_head splits a head (root) block.  It increases the height
+ * of skip list by 1.
+ *
+ * It returns 0 if it succeeds, or one of the following negative error
+ * codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *   Out of memory.
+ */
+static int psl_split_head(ngtcp2_psl *psl) {
+  ngtcp2_psl_blk *rblk = NULL, *lblk, *nhead = NULL;
+
+  rblk = psl_split_blk(psl, psl->head);
+  if (rblk == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  lblk = psl->head;
+
+  nhead = ngtcp2_mem_malloc(psl->mem, sizeof(ngtcp2_psl_blk));
+  if (nhead == NULL) {
+    ngtcp2_mem_free(psl->mem, rblk);
+    return NGTCP2_ERR_NOMEM;
+  }
+  nhead->next = NULL;
+  nhead->n = 2;
+  nhead->leaf = 0;
+
+  nhead->nodes[0].range = lblk->nodes[lblk->n - 1].range;
+  nhead->nodes[0].blk = lblk;
+  nhead->nodes[1].range = rblk->nodes[rblk->n - 1].range;
+  nhead->nodes[1].blk = rblk;
+
+  psl->head = nhead;
+
+  return 0;
+}
+
+/*
+ * insert_node inserts a node whose range is |range| with the
+ * associated |data| at the index of |i|.  This function assumes that
+ * the number of nodes contained by |blk| is strictly less than
+ * NGTCP2_PSL_NBLK.
+ */
+static void insert_node(ngtcp2_psl_blk *blk, size_t i,
+                        const ngtcp2_range *range, void *data) {
+  ngtcp2_psl_node *node;
+
+  assert(blk->n < NGTCP2_PSL_NBLK);
+
+  memmove(&blk->nodes[i + 1], &blk->nodes[i],
+          sizeof(ngtcp2_psl_node) * (blk->n - i));
+
+  node = &blk->nodes[i];
+  node->range = *range;
+  node->data = data;
+
+  ++blk->n;
+}
+
+static int range_intersect(const ngtcp2_range *a, const ngtcp2_range *b) {
+  return ngtcp2_max(a->begin, b->begin) < ngtcp2_min(a->end, b->end);
+}
+
+int ngtcp2_psl_insert(ngtcp2_psl *psl, ngtcp2_psl_it *it,
+                      const ngtcp2_range *range, void *data) {
+  ngtcp2_psl_blk *blk = psl->head;
+  ngtcp2_psl_node *node;
+  size_t i;
+  int rv;
+
+  if (blk->n + 1 == NGTCP2_PSL_NBLK) {
+    rv = psl_split_head(psl);
+    if (rv != 0) {
+      return rv;
+    }
+    blk = psl->head;
+  }
+
+  for (;;) {
+    for (i = 0, node = &blk->nodes[i]; node->range.begin < range->begin;
+         ++i, ++node)
+      ;
+
+    assert(!range_intersect(&node->range, range));
+
+    if (blk->leaf) {
+      insert_node(blk, i, range, data);
+      if (it) {
+        ngtcp2_psl_it_init(it, blk, i);
+      }
+      return 0;
+    }
+
+    if (node->blk->n + 1 == NGTCP2_PSL_NBLK) {
+      rv = psl_split_node(psl, blk, i);
+      if (rv != 0) {
+        return rv;
+      }
+      if (node->range.begin < range->begin) {
+        node = &blk->nodes[i + 1];
+      }
+    }
+
+    blk = node->blk;
+  }
+}
+
+/*
+ * remove_node removes the node included in |blk| at the index of |i|.
+ */
+static void remove_node(ngtcp2_psl_blk *blk, size_t i) {
+  memmove(&blk->nodes[i], &blk->nodes[i + 1],
+          sizeof(ngtcp2_psl_node) * (blk->n - (i + 1)));
+
+  --blk->n;
+}
+
+/*
+ * psl_merge_node merges 2 nodes which are the nodes at the index of
+ * |i| and |i + 1|.
+ *
+ * If |blk| is the direct descendant of head (root) block and the head
+ * block contains just 2 nodes, the merged block becomes head block,
+ * which decreases the height of |psl| by 1.
+ *
+ * This function returns the pointer to the merged block.
+ */
+static ngtcp2_psl_blk *psl_merge_node(ngtcp2_psl *psl, ngtcp2_psl_blk *blk,
+                                      size_t i) {
+  ngtcp2_psl_blk *lblk, *rblk;
+
+  assert(i + 1 < blk->n);
+
+  lblk = blk->nodes[i].blk;
+  rblk = blk->nodes[i + 1].blk;
+
+  assert(lblk->n + rblk->n < NGTCP2_PSL_NBLK);
+
+  memcpy(&lblk->nodes[lblk->n], &rblk->nodes[0],
+         sizeof(ngtcp2_psl_node) * rblk->n);
+
+  lblk->n += rblk->n;
+  lblk->next = rblk->next;
+
+  ngtcp2_mem_free(psl->mem, rblk);
+
+  if (psl->head == blk && blk->n == 2) {
+    ngtcp2_mem_free(psl->mem, psl->head);
+    psl->head = lblk;
+  } else {
+    remove_node(blk, i + 1);
+    blk->nodes[i].range = lblk->nodes[lblk->n - 1].range;
+  }
+
+  return lblk;
+}
+
+/*
+ * psl_relocate_node moves the node located at the index of |i| in
+ * |blk| to the next block.
+ *
+ * It returns the index of the block in |blk| where the node is moved.
+ */
+static size_t psl_relocate_node(ngtcp2_psl *psl, ngtcp2_psl_blk *blk,
+                                size_t i) {
+  ngtcp2_psl_node *node = &blk->nodes[i];
+  ngtcp2_psl_node *rnode = &blk->nodes[i + 1];
+
+  assert(blk->n > i + 1);
+
+  memmove(&rnode->blk->nodes[1], &rnode->blk->nodes[0],
+          sizeof(ngtcp2_psl_node) * rnode->blk->n);
+
+  rnode->blk->nodes[0] = node->blk->nodes[node->blk->n - 1];
+  ++rnode->blk->n;
+
+  --node->blk->n;
+
+  if (node->blk->n == 0) {
+    ngtcp2_mem_free(psl->mem, node->blk);
+    memmove(&blk->nodes[i], &blk->nodes[i + 1],
+            sizeof(ngtcp2_psl_node) * (blk->n - (i + 1)));
+    --blk->n;
+    return i;
+  }
+
+  node->range = node->blk->nodes[node->blk->n - 1].range;
+
+  return i + 1;
+}
+
+ngtcp2_psl_it ngtcp2_psl_remove(ngtcp2_psl *psl, const ngtcp2_range *range) {
+  ngtcp2_psl_blk *blk = psl->head, *lblk, *rblk;
+  ngtcp2_psl_node *node;
+  size_t i, j;
+  ngtcp2_psl_it it;
+
+  for (;;) {
+    for (i = 0, node = &blk->nodes[i]; node->range.begin < range->begin;
+         ++i, ++node)
+      ;
+
+    if (blk->leaf) {
+      assert(i < blk->n);
+      remove_node(blk, i);
+      if (blk->n == i) {
+        ngtcp2_psl_it_init(&it, blk->next, 0);
+      } else {
+        ngtcp2_psl_it_init(&it, blk, i);
+      }
+      return it;
+    }
+
+    if (ngtcp2_range_equal(&node->range, range)) {
+      i = psl_relocate_node(psl, blk, i);
+      node = &blk->nodes[i];
+    }
+
+    if (blk->n >= 2 && blk->n < NGTCP2_PSL_NBLK / 2) {
+      j = i == 0 ? 0 : i - 1;
+
+      lblk = blk->nodes[j].blk;
+      rblk = blk->nodes[j + 1].blk;
+
+      assert(lblk->n);
+      assert(rblk->n);
+
+      if (lblk->n + rblk->n < NGTCP2_PSL_NBLK) {
+        blk = psl_merge_node(psl, blk, j);
+      } else {
+        blk = node->blk;
+      }
+    } else {
+      blk = node->blk;
+    }
+  }
+}
+
+ngtcp2_psl_it ngtcp2_psl_lower_bound(ngtcp2_psl *psl,
+                                     const ngtcp2_range *range) {
+  ngtcp2_psl_blk *blk = psl->head;
+  ngtcp2_psl_node *node;
+  size_t i;
+
+  for (;;) {
+    for (i = 0, node = &blk->nodes[i]; node->range.begin < range->begin &&
+                                       !range_intersect(&node->range, range);
+         ++i, node = &blk->nodes[i])
+      ;
+
+    if (blk->leaf) {
+      ngtcp2_psl_it it = {blk, i};
+      return it;
+    }
+
+    blk = node->blk;
+  }
+}
+
+void ngtcp2_psl_update_range(ngtcp2_psl *psl, const ngtcp2_range *old_range,
+                             const ngtcp2_range *new_range) {
+  ngtcp2_psl_blk *blk = psl->head;
+  ngtcp2_psl_node *node;
+  size_t i;
+
+  assert(old_range->begin <= new_range->begin);
+  assert(new_range->end <= old_range->end);
+
+  for (;;) {
+    for (i = 0, node = &blk->nodes[i]; node->range.begin < old_range->begin;
+         ++i, node = &blk->nodes[i])
+      ;
+
+    if (blk->leaf) {
+      assert(ngtcp2_range_equal(&node->range, old_range));
+      node->range = *new_range;
+      return;
+    }
+
+    if (ngtcp2_range_equal(&node->range, old_range)) {
+      node->range = *new_range;
+    } else {
+      assert(!range_intersect(&node->range, old_range));
+    }
+
+    blk = node->blk;
+  }
+}
+
+static void psl_print(ngtcp2_psl *psl, const ngtcp2_psl_blk *blk,
+                      size_t level) {
+  size_t i;
+
+  fprintf(stderr, "LV=%zu n=%zu\n", level, blk->n);
+
+  if (blk->leaf) {
+    for (i = 0; i < blk->n; ++i) {
+      fprintf(stderr, " [%" PRIu64 ", %" PRIu64 ")", blk->nodes[i].range.begin,
+              blk->nodes[i].range.end);
+    }
+    fprintf(stderr, "\n");
+    return;
+  }
+
+  for (i = 0; i < blk->n; ++i) {
+    psl_print(psl, blk->nodes[i].blk, level + 1);
+  }
+}
+
+void ngtcp2_psl_print(ngtcp2_psl *psl) { psl_print(psl, psl->head, 0); }
+
+ngtcp2_psl_it ngtcp2_psl_begin(const ngtcp2_psl *psl) {
+  const ngtcp2_psl_blk *blk = psl->head;
+
+  for (;;) {
+    if (blk->leaf) {
+      ngtcp2_psl_it it = {blk, 0};
+      return it;
+    }
+    blk = blk->nodes[0].blk;
+  }
+}
+
+void ngtcp2_psl_it_init(ngtcp2_psl_it *it, const ngtcp2_psl_blk *blk,
+                        size_t i) {
+  it->blk = blk;
+  it->i = i;
+}
+
+void *ngtcp2_psl_it_get(const ngtcp2_psl_it *it) {
+  return it->blk->nodes[it->i].data;
+}
+
+void ngtcp2_psl_it_next(ngtcp2_psl_it *it) {
+  assert(!ngtcp2_psl_it_end(it));
+
+  if (++it->i == it->blk->n) {
+    it->blk = it->blk->next;
+    it->i = 0;
+  }
+}
+
+int ngtcp2_psl_it_end(const ngtcp2_psl_it *it) {
+  ngtcp2_range end = {UINT64_MAX, UINT64_MAX};
+  return ngtcp2_range_equal(&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;
+}
diff --git a/lib/ngtcp2_psl.h b/lib/ngtcp2_psl.h
new file mode 100644
index 00000000..4a900932
--- /dev/null
+++ b/lib/ngtcp2_psl.h
@@ -0,0 +1,217 @@
+/*
+ * 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_PSL_H
+#define NGTCP2_PSL_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdlib.h>
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_range.h"
+
+/*
+ * Skip List implementation inspired by
+ * https://github.com/jabr/olio/blob/master/skiplist.c
+ *
+ * Removed rebalancing because it destroys the invariant that the
+ * range of node must be the same as the range of its last direct
+ * descendant.  It seems that the original code also fails to maintain
+ * it when deleting a node.  This implementation fixes these issues.
+ */
+
+/* NGTCP2_PSL_NBLK is the maximum number of nodes which a single block
+   can contain.  It contains normally one less nodes because we have
+   to allocate one empty slot when deleting the last node. */
+#define NGTCP2_PSL_NBLK 16
+
+struct ngtcp2_psl_node;
+typedef struct ngtcp2_psl_node ngtcp2_psl_node;
+
+struct ngtcp2_psl_blk;
+typedef struct ngtcp2_psl_blk ngtcp2_psl_blk;
+
+/*
+ * ngtcp2_psl_node is a node which contains either ngtcp2_psl_blk or
+ * opaque data.  If a node is an internal node, it contains
+ * ngtcp2_psl_blk.  Otherwise, it has data.  The invariant is that the
+ * range of internal node dictates the maximum range in its
+ * descendants, and the corresponding leaf node must exist.
+ */
+struct ngtcp2_psl_node {
+  ngtcp2_range range;
+  union {
+    ngtcp2_psl_blk *blk;
+    void *data;
+  };
+};
+
+/*
+ * ngtcp2_psl_blk contains ngtcp2_psl_node objects.
+ */
+struct ngtcp2_psl_blk {
+  /* next points to the next block if leaf field is nonzero. */
+  ngtcp2_psl_blk *next;
+  /* n is the number of nodes this object contains in nodes. */
+  size_t n;
+  /* leaf is nonzero if this block contains leaf nodes. */
+  int leaf;
+  ngtcp2_psl_node nodes[NGTCP2_PSL_NBLK];
+};
+
+struct ngtcp2_psl_it;
+typedef struct ngtcp2_psl_it ngtcp2_psl_it;
+
+/*
+ * ngtcp2_psl_it is a forward iterator to iterate nodes.
+ */
+struct ngtcp2_psl_it {
+  const ngtcp2_psl_blk *blk;
+  size_t i;
+};
+
+struct ngtcp2_psl;
+typedef struct ngtcp2_psl ngtcp2_psl;
+
+/*
+ * ngtcp2_psl is a deterministic paged skip list.
+ */
+struct ngtcp2_psl {
+  /* head points to the root block. */
+  ngtcp2_psl_blk *head;
+  ngtcp2_mem *mem;
+};
+
+/*
+ * ngtcp2_psl_init initializes |psl|.
+ *
+ * It returns 0 if it succeeds, or one of the following negative error
+ * codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *   Out of memory.
+ */
+int ngtcp2_psl_init(ngtcp2_psl *psl, ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_psl_free frees resources allocated for |psl|.  If |psl| is
+ * NULL, this function does nothing.  It does not free the memory
+ * region pointed by |psl| itself.
+ */
+void ngtcp2_psl_free(ngtcp2_psl *psl);
+
+/*
+ * ngtcp2_psl_insert inserts |range| with its associated |data|.  On
+ * successful insertion, the iterator points to the inserted node is
+ * stored in |*it|.
+ *
+ * This function assumes that the existing ranges do not intersect
+ * with |range|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *   Out of memory.
+ */
+int ngtcp2_psl_insert(ngtcp2_psl *psl, ngtcp2_psl_it *it,
+                      const ngtcp2_range *range, void *data);
+
+/*
+ * ngtcp2_psl_remove removes the |range| from |psl|.  It assumes such
+ * the range is included in |psl|.
+ *
+ * This function returns the iterator which points to the node which
+ * is located at the right next of the removed node.
+ */
+ngtcp2_psl_it ngtcp2_psl_remove(ngtcp2_psl *psl, const ngtcp2_range *range);
+
+/*
+ * ngtcp2_psl_update_range replaces the range of nodes which has
+ * |old_range| with |new_range|.  |old_range| must include
+ * |new_range|.
+ */
+void ngtcp2_psl_update_range(ngtcp2_psl *psl, const ngtcp2_range *old_range,
+                             const ngtcp2_range *new_range);
+
+/*
+ * ngtcp2_psl_lower_bound returns the iterator which points to the
+ * first node whose range intersects with |range|.  If there is no
+ * such node, it returns the iterator which satisfies
+ * ngtcp2_psl_it_end(it) != 0.
+ */
+ngtcp2_psl_it ngtcp2_psl_lower_bound(ngtcp2_psl *psl,
+                                     const ngtcp2_range *range);
+
+/*
+ * ngtcp2_psl_begin returns the iterator which points to the first
+ * node.  If there is no node in |psl|, it returns the iterator which
+ * satisfies ngtcp2_psl_it_end(it) != 0.
+ */
+ngtcp2_psl_it ngtcp2_psl_begin(const ngtcp2_psl *psl);
+
+/*
+ * ngtcp2_psl_print prints its internal state in stderr.  This
+ * function should be used for the debugging purpose only.
+ */
+void ngtcp2_psl_print(ngtcp2_psl *psl);
+
+/*
+ * ngtcp2_psl_it_init initializes |it|.
+ */
+void ngtcp2_psl_it_init(ngtcp2_psl_it *it, const ngtcp2_psl_blk *blk, size_t i);
+
+/*
+ * ngtcp2_psl_it_get returns the data associated to the node which
+ * |it| points to.  If this function is called when
+ * ngtcp2_psl_it_end(it) returns nonzero, it returns NULL.
+ */
+void *ngtcp2_psl_it_get(const ngtcp2_psl_it *it);
+
+/*
+ * ngtcp2_psl_it_next advances the iterator by one.  It is undefined
+ * if this function is called when ngtcp2_psl_it_end(it) returns
+ * nonzero.
+ */
+void ngtcp2_psl_it_next(ngtcp2_psl_it *it);
+
+/*
+ * ngtcp2_psl_it_end returns nonzero if |it| points to the beyond the
+ * last node.
+ */
+int ngtcp2_psl_it_end(const ngtcp2_psl_it *it);
+
+/*
+ * ngtcp2_psl_range returns the range of the node which |it| points
+ * to.  It is OK to call this function when ngtcp2_psl_it_end(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);
+
+#endif /* NGTCP2_PSL_H */
diff --git a/lib/ngtcp2_rob.c b/lib/ngtcp2_rob.c
index 6d83c2c6..18cab144 100644
--- a/lib/ngtcp2_rob.c
+++ b/lib/ngtcp2_rob.c
@@ -36,8 +36,8 @@ int ngtcp2_rob_gap_new(ngtcp2_rob_gap **pg, uint64_t begin, uint64_t end,
     return NGTCP2_ERR_NOMEM;
   }
 
-  ngtcp2_range_init(&(*pg)->range, begin, end);
-  (*pg)->next = NULL;
+  (*pg)->range.begin = begin;
+  (*pg)->range.end = end;
 
   return 0;
 }
@@ -53,10 +53,10 @@ int ngtcp2_rob_data_new(ngtcp2_rob_data **pd, uint64_t offset, size_t chunk,
     return NGTCP2_ERR_NOMEM;
   }
 
+  (*pd)->range.begin = offset;
+  (*pd)->range.end = offset + chunk;
   (*pd)->begin = (uint8_t *)(*pd) + sizeof(ngtcp2_rob_data);
   (*pd)->end = (*pd)->begin + chunk;
-  (*pd)->offset = offset;
-  (*pd)->next = NULL;
 
   return 0;
 }
@@ -67,85 +67,96 @@ void ngtcp2_rob_data_del(ngtcp2_rob_data *d, ngtcp2_mem *mem) {
 
 int ngtcp2_rob_init(ngtcp2_rob *rob, size_t chunk, ngtcp2_mem *mem) {
   int rv;
+  ngtcp2_rob_gap *g;
 
-  rv = ngtcp2_rob_gap_new(&rob->gap, 0, UINT64_MAX, mem);
+  rv = ngtcp2_psl_init(&rob->gappsl, mem);
   if (rv != 0) {
-    return rv;
+    goto fail_gappsl_psl_init;
+  }
+
+  rv = ngtcp2_rob_gap_new(&g, 0, UINT64_MAX, mem);
+  if (rv != 0) {
+    goto fail_rob_gap_new;
+  }
+
+  rv = ngtcp2_psl_insert(&rob->gappsl, NULL, &g->range, g);
+  if (rv != 0) {
+    goto fail_gappsl_psl_insert;
+  }
+
+  rv = ngtcp2_psl_init(&rob->datapsl, mem);
+  if (rv != 0) {
+    goto fail_datapsl_psl_init;
   }
 
-  rob->data = NULL;
   rob->chunk = chunk;
   rob->mem = mem;
 
   return 0;
+
+fail_datapsl_psl_init:
+fail_gappsl_psl_insert:
+  ngtcp2_rob_gap_del(g, mem);
+fail_rob_gap_new:
+  ngtcp2_psl_free(&rob->gappsl);
+fail_gappsl_psl_init:
+  return rv;
 }
 
 void ngtcp2_rob_free(ngtcp2_rob *rob) {
-  ngtcp2_rob_gap *g, *ng;
-  ngtcp2_rob_data *d, *nd;
+  static const ngtcp2_range r = {0, 0};
+  ngtcp2_psl_it it;
 
   if (rob == NULL) {
     return;
   }
 
-  for (g = rob->gap; g;) {
-    ng = g->next;
-    ngtcp2_rob_gap_del(g, rob->mem);
-    g = ng;
+  for (it = ngtcp2_psl_lower_bound(&rob->datapsl, &r); !ngtcp2_psl_it_end(&it);
+       ngtcp2_psl_it_next(&it)) {
+    ngtcp2_rob_data_del(ngtcp2_psl_it_get(&it), rob->mem);
   }
-  for (d = rob->data; d;) {
-    nd = d->next;
-    ngtcp2_rob_data_del(d, rob->mem);
-    d = nd;
-  }
-}
-
-static void insert_gap(ngtcp2_rob_gap **pg, ngtcp2_rob_gap *g) {
-  g->next = (*pg)->next;
-  (*pg)->next = g;
-}
 
-static void remove_gap(ngtcp2_rob_gap **pg, ngtcp2_mem *mem) {
-  ngtcp2_rob_gap *g = *pg;
-  *pg = g->next;
-  ngtcp2_rob_gap_del(g, mem);
-}
+  for (it = ngtcp2_psl_lower_bound(&rob->gappsl, &r); !ngtcp2_psl_it_end(&it);
+       ngtcp2_psl_it_next(&it)) {
+    ngtcp2_rob_gap_del(ngtcp2_psl_it_get(&it), rob->mem);
+  }
 
-static void remove_data(ngtcp2_rob_data **pd, ngtcp2_mem *mem) {
-  ngtcp2_rob_data *d = *pd;
-  *pd = d->next;
-  ngtcp2_rob_data_del(d, mem);
+  ngtcp2_psl_free(&rob->datapsl);
+  ngtcp2_psl_free(&rob->gappsl);
 }
 
-static int rob_write_data(ngtcp2_rob *rob, ngtcp2_rob_data **pd,
-                          uint64_t offset, const uint8_t *data, size_t len) {
+static int rob_write_data(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data,
+                          size_t len) {
   size_t n;
   int rv;
-  ngtcp2_rob_data *nd;
+  ngtcp2_rob_data *d;
+  ngtcp2_range range = {offset, offset + len};
+  ngtcp2_psl_it it;
 
-  for (;;) {
-    if (*pd == NULL || offset < (*pd)->offset) {
-      rv = ngtcp2_rob_data_new(&nd, (offset / rob->chunk) * rob->chunk,
+  for (it = ngtcp2_psl_lower_bound(&rob->datapsl, &range); len;
+       ngtcp2_psl_it_next(&it)) {
+    d = ngtcp2_psl_it_get(&it);
+
+    if (d == NULL || offset < d->range.begin) {
+      rv = ngtcp2_rob_data_new(&d, (offset / rob->chunk) * rob->chunk,
                                rob->chunk, rob->mem);
       if (rv != 0) {
         return rv;
       }
-      /* insert before *pd */
-      nd->next = *pd;
-      *pd = nd;
-    } else if ((*pd)->offset + rob->chunk < offset) {
-      pd = &(*pd)->next;
-      continue;
+
+      rv = ngtcp2_psl_insert(&rob->datapsl, &it, &d->range, d);
+      if (rv != 0) {
+        ngtcp2_rob_data_del(d, rob->mem);
+        return rv;
+      }
+    } else if (d->range.begin + rob->chunk < offset) {
+      assert(0);
     }
-    n = ngtcp2_min(len, (*pd)->offset + rob->chunk - offset);
-    memcpy((*pd)->begin + (offset - (*pd)->offset), data, n);
+    n = ngtcp2_min(len, d->range.begin + rob->chunk - offset);
+    memcpy(d->begin + (offset - d->range.begin), data, n);
     offset += n;
     data += n;
     len -= n;
-    if (len == 0) {
-      return 0;
-    }
-    pd = &(*pd)->next;
   }
 
   return 0;
@@ -154,111 +165,150 @@ static int rob_write_data(ngtcp2_rob *rob, ngtcp2_rob_data **pd,
 int ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data,
                     size_t datalen) {
   int rv;
-  ngtcp2_rob_gap **pg;
+  ngtcp2_rob_gap *g;
   ngtcp2_range m, l, r, q = {offset, offset + datalen};
-  ngtcp2_rob_data **pd = &rob->data;
-
-  for (pg = &rob->gap; *pg;) {
-    m = ngtcp2_range_intersect(&q, &(*pg)->range);
-    if (ngtcp2_range_len(&m)) {
-      if (ngtcp2_range_equal(&(*pg)->range, &m)) {
-        remove_gap(pg, rob->mem);
-        rv = rob_write_data(rob, pd, m.begin, data + (m.begin - offset),
-                            ngtcp2_range_len(&m));
+  ngtcp2_psl_it it;
+
+  it = ngtcp2_psl_lower_bound(&rob->gappsl, &q);
+
+  for (; !ngtcp2_psl_it_end(&it);) {
+    g = ngtcp2_psl_it_get(&it);
+
+    m = ngtcp2_range_intersect(&q, &g->range);
+    if (!ngtcp2_range_len(&m)) {
+      break;
+    }
+    if (ngtcp2_range_equal(&g->range, &m)) {
+      it = ngtcp2_psl_remove(&rob->gappsl, &g->range);
+      ngtcp2_rob_gap_del(g, rob->mem);
+      rv = rob_write_data(rob, m.begin, data + (m.begin - offset),
+                          ngtcp2_range_len(&m));
+      if (rv != 0) {
+        return rv;
+      }
+
+      continue;
+    }
+    ngtcp2_range_cut(&l, &r, &g->range, &m);
+    if (ngtcp2_range_len(&l)) {
+      ngtcp2_psl_update_range(&rob->gappsl, &g->range, &l);
+      g->range = l;
+
+      if (ngtcp2_range_len(&r)) {
+        ngtcp2_rob_gap *ng;
+        rv = ngtcp2_rob_gap_new(&ng, r.begin, r.end, rob->mem);
         if (rv != 0) {
           return rv;
         }
-        continue;
-      }
-      ngtcp2_range_cut(&l, &r, &(*pg)->range, &m);
-      if (ngtcp2_range_len(&l)) {
-        (*pg)->range = l;
-
-        if (ngtcp2_range_len(&r)) {
-          ngtcp2_rob_gap *ng;
-          rv = ngtcp2_rob_gap_new(&ng, r.begin, r.end, rob->mem);
-          if (rv != 0) {
-            return rv;
-          }
-          insert_gap(pg, ng);
-          pg = &((*pg)->next);
+        rv = ngtcp2_psl_insert(&rob->gappsl, &it, &ng->range, ng);
+        if (rv != 0) {
+          ngtcp2_rob_gap_del(ng, rob->mem);
+          return rv;
         }
-      } else if (ngtcp2_range_len(&r)) {
-        (*pg)->range = r;
-      }
-      rv = rob_write_data(rob, pd, m.begin, data + (m.begin - offset),
-                          ngtcp2_range_len(&m));
-      if (rv != 0) {
-        return rv;
       }
+    } else if (ngtcp2_range_len(&r)) {
+      ngtcp2_psl_update_range(&rob->gappsl, &g->range, &r);
+      g->range = r;
     }
-    if (ngtcp2_range_not_after(&q, &(*pg)->range)) {
-      break;
+    rv = rob_write_data(rob, m.begin, data + (m.begin - offset),
+                        ngtcp2_range_len(&m));
+    if (rv != 0) {
+      return rv;
     }
-    pg = &((*pg)->next);
+    ngtcp2_psl_it_next(&it);
   }
   return 0;
 }
 
 void ngtcp2_rob_remove_prefix(ngtcp2_rob *rob, uint64_t offset) {
-  ngtcp2_rob_gap **pg;
-  ngtcp2_rob_data **pd;
+  ngtcp2_rob_gap *g;
+  ngtcp2_rob_data *d;
+  ngtcp2_psl_it it;
+
+  it = ngtcp2_psl_begin(&rob->gappsl);
 
-  for (pg = &rob->gap; *pg;) {
-    if (offset <= (*pg)->range.begin) {
+  for (; !ngtcp2_psl_it_end(&it);) {
+    g = ngtcp2_psl_it_get(&it);
+    if (offset <= g->range.begin) {
       break;
     }
-    if (offset < (*pg)->range.end) {
-      (*pg)->range.begin = offset;
+    if (offset < g->range.end) {
+      ngtcp2_range r = {offset, g->range.end};
+      ngtcp2_psl_update_range(&rob->gappsl, &g->range, &r);
+      g->range.begin = offset;
       break;
     }
-    remove_gap(pg, rob->mem);
+    it = ngtcp2_psl_remove(&rob->gappsl, &g->range);
+    ngtcp2_rob_gap_del(g, rob->mem);
   }
 
-  for (pd = &rob->data; *pd;) {
-    if (offset <= (*pd)->offset) {
-      return;
-    }
-    if (offset < (*pd)->offset + rob->chunk) {
+  it = ngtcp2_psl_begin(&rob->datapsl);
+
+  for (; !ngtcp2_psl_it_end(&it);) {
+    d = ngtcp2_psl_it_get(&it);
+    if (offset < d->range.begin + rob->chunk) {
       return;
     }
-    remove_data(pd, rob->mem);
+    it = ngtcp2_psl_remove(&rob->datapsl, &d->range);
+    ngtcp2_rob_data_del(d, rob->mem);
   }
 }
 
 size_t ngtcp2_rob_data_at(ngtcp2_rob *rob, const uint8_t **pdest,
                           uint64_t offset) {
-  ngtcp2_rob_gap *g = rob->gap;
-  ngtcp2_rob_data *d = rob->data;
+  ngtcp2_rob_gap *g;
+  ngtcp2_rob_data *d;
+  ngtcp2_psl_it it;
+
+  it = ngtcp2_psl_begin(&rob->gappsl);
+  if (ngtcp2_psl_it_end(&it)) {
+    return 0;
+  }
+
+  g = ngtcp2_psl_it_get(&it);
 
   if (g->range.begin <= offset) {
     return 0;
   }
 
+  it = ngtcp2_psl_begin(&rob->datapsl);
+  d = ngtcp2_psl_it_get(&it);
+
   assert(d);
-  assert(d->offset <= offset);
-  assert(offset < d->offset + rob->chunk);
+  assert(d->range.begin <= offset);
+  assert(offset < d->range.begin + rob->chunk);
 
-  *pdest = d->begin + (offset - d->offset);
+  *pdest = d->begin + (offset - d->range.begin);
 
-  return ngtcp2_min(g->range.begin, d->offset + rob->chunk) - offset;
+  return ngtcp2_min(g->range.begin, d->range.begin + rob->chunk) - offset;
 }
 
 void ngtcp2_rob_pop(ngtcp2_rob *rob, uint64_t offset, size_t len) {
-  ngtcp2_rob_data **pd = &rob->data;
+  ngtcp2_psl_it it;
+  ngtcp2_rob_data *d;
 
-  assert(*pd);
+  it = ngtcp2_psl_begin(&rob->datapsl);
+  d = ngtcp2_psl_it_get(&it);
+
+  assert(d);
 
-  if (offset + len < (*pd)->offset + rob->chunk) {
+  if (offset + len < d->range.begin + rob->chunk) {
     return;
   }
 
-  remove_data(pd, rob->mem);
+  ngtcp2_psl_remove(&rob->datapsl, &d->range);
+  ngtcp2_rob_data_del(d, rob->mem);
 }
 
 uint64_t ngtcp2_rob_first_gap_offset(ngtcp2_rob *rob) {
-  if (rob->gap) {
-    return rob->gap->range.begin;
+  ngtcp2_psl_it it = ngtcp2_psl_begin(&rob->gappsl);
+  ngtcp2_rob_gap *g;
+
+  if (ngtcp2_psl_it_end(&it)) {
+    return UINT64_MAX;
   }
-  return UINT64_MAX;
+
+  g = ngtcp2_psl_it_get(&it);
+
+  return g->range.begin;
 }
diff --git a/lib/ngtcp2_rob.h b/lib/ngtcp2_rob.h
index 42600a4b..c514c8c0 100644
--- a/lib/ngtcp2_rob.h
+++ b/lib/ngtcp2_rob.h
@@ -33,6 +33,7 @@
 
 #include "ngtcp2_mem.h"
 #include "ngtcp2_range.h"
+#include "ngtcp2_psl.h"
 
 struct ngtcp2_rob_gap;
 typedef struct ngtcp2_rob_gap ngtcp2_rob_gap;
@@ -42,10 +43,6 @@ typedef struct ngtcp2_rob_gap ngtcp2_rob_gap;
  * data that is not received yet.
  */
 struct ngtcp2_rob_gap {
-  /* next points to the next gap.  This singly linked list is ordered
-     by range.begin in the increasing order, and they never
-     overlap. */
-  ngtcp2_rob_gap *next;
   /* range is the range of this gap. */
   ngtcp2_range range;
 };
@@ -79,15 +76,12 @@ typedef struct ngtcp2_rob_data ngtcp2_rob_data;
  * ngtcp2_rob_data holds the buffered stream data.
  */
 struct ngtcp2_rob_data {
-  /* next points to the next data.  This singly linked list is ordered
-     by offset in the increasing order, and they never overlap. */
-  ngtcp2_rob_data *next;
+  /* range is the range of this gap. */
+  ngtcp2_range range;
   /* begin points to the buffer. */
   uint8_t *begin;
   /* end points to the one beyond of the last byte of the buffer */
   uint8_t *end;
-  /* offset is a stream offset of begin. */
-  uint64_t offset;
 };
 
 /*
@@ -119,12 +113,12 @@ void ngtcp2_rob_data_del(ngtcp2_rob_data *d, ngtcp2_mem *mem);
  * received in out of order.
  */
 typedef struct {
-  /* gap maintains the range of offset which is not received
+  /* gappsl maintains the range of offset which is not received
      yet. Initially, its range is [0, UINT64_MAX). */
-  ngtcp2_rob_gap *gap;
-  /* data maintains the list of buffers which store received data
+  ngtcp2_psl gappsl;
+  /* datapsl maintains the list of buffers which store received data
      ordered by stream offset. */
-  ngtcp2_rob_data *data;
+  ngtcp2_psl datapsl;
   /* mem is custom memory allocator */
   ngtcp2_mem *mem;
   /* chunk is the size of each buffer in data field */
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index a61b6b7d..32265e0f 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -44,6 +44,7 @@ if(HAVE_CUNIT)
     ngtcp2_ringbuf_test.c
     ngtcp2_conv_test.c
     ngtcp2_test_helper.c
+    ngtcp2_psl_test.c
   )
 
   add_executable(main EXCLUDE_FROM_ALL
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 8e581a2d..b9aeeae2 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -39,6 +39,7 @@ OBJECTS = \
 	ngtcp2_conn_test.c \
 	ngtcp2_ringbuf_test.c \
 	ngtcp2_conv_test.c \
+	ngtcp2_psl_test.c \
 	ngtcp2_test_helper.c
 HFILES= \
 	ngtcp2_pkt_test.h \
@@ -52,6 +53,7 @@ HFILES= \
 	ngtcp2_conn_test.h \
 	ngtcp2_ringbuf_test.h \
 	ngtcp2_conv_test.h \
+	ngtcp2_psl_test.h \
 	ngtcp2_test_helper.h
 
 main_SOURCES = $(HFILES) $(OBJECTS)
diff --git a/tests/main.c b/tests/main.c
index ed29e636..071233ea 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -41,6 +41,7 @@
 #include "ngtcp2_conn_test.h"
 #include "ngtcp2_ringbuf_test.h"
 #include "ngtcp2_conv_test.h"
+#include "ngtcp2_psl_test.h"
 
 static int init_suite1(void) { return 0; }
 
@@ -125,7 +126,9 @@ int main() {
       !CU_add_test(pSuite, "range_intersect", test_ngtcp2_range_intersect) ||
       !CU_add_test(pSuite, "range_cut", test_ngtcp2_range_cut) ||
       !CU_add_test(pSuite, "range_not_after", test_ngtcp2_range_not_after) ||
+      !CU_add_test(pSuite, "psl_insert", test_ngtcp2_psl_insert) ||
       !CU_add_test(pSuite, "rob_push", test_ngtcp2_rob_push) ||
+      !CU_add_test(pSuite, "rob_push_random", test_ngtcp2_rob_push_random) ||
       !CU_add_test(pSuite, "rob_data_at", test_ngtcp2_rob_data_at) ||
       !CU_add_test(pSuite, "rob_remove_prefix",
                    test_ngtcp2_rob_remove_prefix) ||
diff --git a/tests/ngtcp2_psl_test.c b/tests/ngtcp2_psl_test.c
new file mode 100644
index 00000000..48702aa2
--- /dev/null
+++ b/tests/ngtcp2_psl_test.c
@@ -0,0 +1,131 @@
+/*
+ * 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_psl_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include "ngtcp2_psl.h"
+#include "ngtcp2_test_helper.h"
+
+void test_ngtcp2_psl_insert(void) {
+  static const ngtcp2_range keys[] = {
+      {10, 11}, {3, 4}, {8, 9},   {11, 12}, {16, 17}, {12, 13},
+      {1, 2},   {5, 6}, {4, 5},   {0, 1},   {13, 14}, {7, 8},
+      {9, 10},  {2, 3}, {14, 15}, {6, 7},   {15, 16}};
+  ngtcp2_psl psl;
+  ngtcp2_mem *mem = ngtcp2_mem_default();
+  size_t i;
+  const ngtcp2_range *pr;
+  ngtcp2_range r;
+  ngtcp2_psl_it it;
+
+  ngtcp2_psl_init(&psl, mem);
+
+  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_equal(&keys[i], ngtcp2_psl_it_range(&it)));
+  }
+
+  for (i = 0; i < arraylen(keys); ++i) {
+    ngtcp2_psl_remove(&psl, &keys[i]);
+    it = ngtcp2_psl_lower_bound(&psl, &keys[i]);
+    pr = ngtcp2_psl_it_range(&it);
+
+    CU_ASSERT(keys[i].end <= pr->begin);
+  }
+
+  ngtcp2_psl_free(&psl);
+
+  /* check the case that the right end range is removed */
+  ngtcp2_psl_init(&psl, mem);
+
+  for (i = 0; i < 16; ++i) {
+    ngtcp2_range_init(&r, i, i + 1);
+    ngtcp2_psl_insert(&psl, NULL, &r, NULL);
+  }
+
+  /* Removing [7, 8) requires relocation */
+  ngtcp2_range_init(&r, 7, 8);
+  it = ngtcp2_psl_remove(&psl, &r);
+  pr = ngtcp2_psl_it_range(&it);
+
+  CU_ASSERT(8 == pr->begin);
+  CU_ASSERT(9 == pr->end);
+
+  it = ngtcp2_psl_lower_bound(&psl, &r);
+  pr = ngtcp2_psl_it_range(&it);
+
+  CU_ASSERT(8 == pr->begin);
+  CU_ASSERT(9 == pr->end);
+
+  pr = &psl.head->nodes[0].range;
+
+  CU_ASSERT(6 == pr->begin);
+  CU_ASSERT(7 == pr->end);
+
+  ngtcp2_psl_free(&psl);
+
+  /* check merge node (head) */
+  ngtcp2_psl_init(&psl, mem);
+
+  for (i = 0; i < 15; ++i) {
+    ngtcp2_range_init(&r, i, i + 1);
+    ngtcp2_psl_insert(&psl, NULL, &r, NULL);
+  }
+
+  /* Removing these 2 nodes kicks merging 2 nodes under head */
+  ngtcp2_range_init(&r, 6, 7);
+  ngtcp2_psl_remove(&psl, &r);
+
+  ngtcp2_range_init(&r, 5, 6);
+  ngtcp2_psl_remove(&psl, &r);
+
+  CU_ASSERT(14 == psl.head->n);
+
+  ngtcp2_psl_free(&psl);
+
+  /* check merge node (non head) */
+  ngtcp2_psl_init(&psl, mem);
+
+  for (i = 0; i < 15 + 8; ++i) {
+    ngtcp2_range_init(&r, i, i + 1);
+    ngtcp2_psl_insert(&psl, NULL, &r, NULL);
+  }
+
+  /* Removing these 2 nodes kicks merging 2 nodes */
+  ngtcp2_range_init(&r, 6, 7);
+  ngtcp2_psl_remove(&psl, &r);
+
+  ngtcp2_range_init(&r, 5, 6);
+  ngtcp2_psl_remove(&psl, &r);
+
+  CU_ASSERT(2 == psl.head->n);
+  CU_ASSERT(14 == psl.head->nodes[0].blk->n);
+  CU_ASSERT(8 == psl.head->nodes[1].blk->n);
+
+  ngtcp2_psl_free(&psl);
+}
diff --git a/tests/ngtcp2_psl_test.h b/tests/ngtcp2_psl_test.h
new file mode 100644
index 00000000..6e491dae
--- /dev/null
+++ b/tests/ngtcp2_psl_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_PSL_TEST_H
+#define NGTCP2_PSL_TEST_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_ngtcp2_psl_insert(void);
+
+#endif /* NGTCP2_PSL_TEST_H */
diff --git a/tests/ngtcp2_rob_test.c b/tests/ngtcp2_rob_test.c
index dc419966..61731042 100644
--- a/tests/ngtcp2_rob_test.c
+++ b/tests/ngtcp2_rob_test.c
@@ -29,6 +29,7 @@
 #include "ngtcp2_rob.h"
 #include "ngtcp2_test_helper.h"
 #include "ngtcp2_mem.h"
+#include "ngtcp2_macro.h"
 
 void test_ngtcp2_rob_push(void) {
   ngtcp2_mem *mem = ngtcp2_mem_default();
@@ -36,6 +37,7 @@ void test_ngtcp2_rob_push(void) {
   int rv;
   uint8_t data[256];
   ngtcp2_rob_gap *g;
+  ngtcp2_psl_it it;
 
   /* Check range overlapping */
   ngtcp2_rob_init(&rob, 64, mem);
@@ -44,27 +46,34 @@ void test_ngtcp2_rob_push(void) {
 
   CU_ASSERT(0 == rv);
 
-  g = rob.gap;
+  it = ngtcp2_psl_begin(&rob.gappsl);
+  g = ngtcp2_psl_it_get(&it);
 
   CU_ASSERT(0 == g->range.begin);
   CU_ASSERT(34567 == g->range.end);
 
-  g = g->next;
+  ngtcp2_psl_it_next(&it);
+  g = ngtcp2_psl_it_get(&it);
 
   CU_ASSERT(34567 + 145 == g->range.begin);
   CU_ASSERT(UINT64_MAX == g->range.end);
-  CU_ASSERT(NULL == g->next);
+
+  ngtcp2_psl_it_next(&it);
+
+  CU_ASSERT(ngtcp2_psl_it_end(&it));
 
   rv = ngtcp2_rob_push(&rob, 34565, data, 1);
 
   CU_ASSERT(0 == rv);
 
-  g = rob.gap;
+  it = ngtcp2_psl_begin(&rob.gappsl);
+  g = ngtcp2_psl_it_get(&it);
 
   CU_ASSERT(0 == g->range.begin);
   CU_ASSERT(34565 == g->range.end);
 
-  g = g->next;
+  ngtcp2_psl_it_next(&it);
+  g = ngtcp2_psl_it_get(&it);
 
   CU_ASSERT(34566 == g->range.begin);
   CU_ASSERT(34567 == g->range.end);
@@ -73,12 +82,14 @@ void test_ngtcp2_rob_push(void) {
 
   CU_ASSERT(0 == rv);
 
-  g = rob.gap;
+  it = ngtcp2_psl_begin(&rob.gappsl);
+  g = ngtcp2_psl_it_get(&it);
 
   CU_ASSERT(0 == g->range.begin);
   CU_ASSERT(34563 == g->range.end);
 
-  g = g->next;
+  ngtcp2_psl_it_next(&it);
+  g = ngtcp2_psl_it_get(&it);
 
   CU_ASSERT(34564 == g->range.begin);
   CU_ASSERT(34565 == g->range.end);
@@ -87,16 +98,21 @@ void test_ngtcp2_rob_push(void) {
 
   CU_ASSERT(0 == rv);
 
-  g = rob.gap;
+  it = ngtcp2_psl_begin(&rob.gappsl);
+  g = ngtcp2_psl_it_get(&it);
 
   CU_ASSERT(0 == g->range.begin);
   CU_ASSERT(34561 == g->range.end);
 
-  g = g->next;
+  ngtcp2_psl_it_next(&it);
+  g = ngtcp2_psl_it_get(&it);
 
   CU_ASSERT(34567 + 145 == g->range.begin);
   CU_ASSERT(UINT64_MAX == g->range.end);
-  CU_ASSERT(NULL == g->next);
+
+  ngtcp2_psl_it_next(&it);
+
+  CU_ASSERT(ngtcp2_psl_it_end(&it));
 
   ngtcp2_rob_free(&rob);
 
@@ -107,11 +123,15 @@ void test_ngtcp2_rob_push(void) {
 
   CU_ASSERT(0 == rv);
 
-  g = rob.gap;
+  it = ngtcp2_psl_begin(&rob.gappsl);
+  g = ngtcp2_psl_it_get(&it);
 
   CU_ASSERT(123 == g->range.begin);
   CU_ASSERT(UINT64_MAX == g->range.end);
-  CU_ASSERT(NULL == g->next);
+
+  ngtcp2_psl_it_next(&it);
+
+  CU_ASSERT(ngtcp2_psl_it_end(&it));
 
   ngtcp2_rob_free(&rob);
 
@@ -122,11 +142,137 @@ void test_ngtcp2_rob_push(void) {
 
   CU_ASSERT(0 == rv);
 
-  g = rob.gap;
+  it = ngtcp2_psl_begin(&rob.gappsl);
+  g = ngtcp2_psl_it_get(&it);
 
   CU_ASSERT(0 == g->range.begin);
   CU_ASSERT(UINT64_MAX - 123 == g->range.end);
-  CU_ASSERT(NULL == g->next);
+
+  ngtcp2_psl_it_next(&it);
+
+  CU_ASSERT(ngtcp2_psl_it_end(&it));
+
+  ngtcp2_rob_free(&rob);
+}
+
+static ngtcp2_range randkeys[] = {
+    {25996, 26260}, {9431, 9555},   {9113, 9417},   {2992, 3408},
+    {35761, 36019}, {38891, 39113}, {30074, 30325}, {9525, 9953},
+    {31708, 31944}, {24554, 24864}, {13097, 13472}, {47253, 47400},
+    {18424, 18742}, {4618, 4889},   {40871, 41076}, {17642, 18068},
+    {47496, 47588}, {1226, 1283},   {17904, 18248}, {9221, 9488},
+    {8621, 8773},   {27912, 28344}, {5878, 6121},   {37336, 37545},
+    {15403, 15557}, {29314, 29450}, {2342, 2595},   {34000, 34356},
+    {46428, 46828}, {40624, 40703}, {47014, 47319}, {13353, 13635},
+    {14466, 14682}, {22446, 22654}, {10035, 10140}, {1005, 1410},
+    {3741, 4133},   {45734, 46053}, {7954, 8214},   {32666, 32796},
+    {45236, 45531}, {32100, 32501}, {25466, 25850}, {2845, 3179},
+    {23525, 23991}, {46367, 46459}, {37712, 38164}, {8506, 8680},
+    {31702, 31752}, {33364, 33825}, {14284, 14614}, {22928, 23344},
+    {29058, 29155}, {36639, 37014}, {29133, 29445}, {31071, 31478},
+    {40074, 40370}, {1263, 1383},   {7908, 8181},   {40426, 40716},
+    {4830, 5053},   {38241, 38645}, {51197, 51401}, {36180, 36301},
+    {14920, 15262}, {5707, 5882},   {32697, 32948}, {42324, 42791},
+    {1543, 1732},   {11037, 11395}, {36534, 36707}, {26093, 26322},
+    {41862, 42213}, {1373, 1745},   {31322, 31706}, {45474, 45851},
+    {19333, 19701}, {49172, 49524}, {10641, 10932}, {17459, 17630},
+    {5560, 5936},   {7657, 7988},   {3300, 3357},   {2496, 2600},
+    {46018, 46173}, {43127, 43239}, {48949, 49036}, {45094, 45412},
+    {8405, 8738},   {8687, 9168},   {41405, 41759}, {22014, 22474},
+    {16097, 16426}, {29611, 29931}, {46054, 46250}, {26305, 26545},
+    {13696, 13964}, {26899, 26981}, {30797, 30936}, {34125, 34235},
+    {50016, 50058}, {46775, 47005}, {4891, 5106},   {12720, 12994},
+    {44623, 44967}, {33597, 34060}, {50796, 51295}, {18862, 19242},
+    {36166, 36249}, {22237, 22583}, {18188, 18586}, {21376, 21447},
+    {49563, 49800}, {10121, 10272}, {39156, 39275}, {17609, 17866},
+    {47609, 47829}, {34311, 34631}, {2144, 2433},   {34692, 34824},
+    {8309, 8476},   {26969, 27447}, {40651, 40952}, {11906, 12116},
+    {22467, 22864}, {35535, 35941}, {33061, 33259}, {21006, 21364},
+    {15212, 15504}, {6954, 7356},   {6126, 6405},   {29268, 29514},
+    {35221, 35505}, {4163, 4350},   {17374, 17519}, {16170, 16511},
+    {37142, 37440}, {6288, 6556},   {27795, 28092}, {35381, 35476},
+    {1186, 1455},   {39834, 40197}, {3471, 3906},   {46871, 47242},
+    {40258, 40406}, {0, 306},       {31852, 32133}, {23314, 23408},
+    {37494, 37625}, {48742, 48990}, {37616, 37905}, {18615, 18991},
+    {2561, 2921},   {47767, 48139}, {39616, 39792}, {44791, 45046},
+    {2770, 3067},   {16697, 17083}, {9216, 9427},   {37661, 37774},
+    {14666, 14976}, {31547, 31819}, {36052, 36356}, {34989, 35285},
+    {1651, 2028},   {36264, 36515}, {10257, 10551}, {24381, 24628},
+    {28428, 28726}, {4242, 4576},   {44972, 45107}, {12970, 13213},
+    {19539, 19828}, {42541, 42763}, {20349, 20630}, {20138, 20418},
+    {10884, 11138}, {2717, 2908},   {8292, 8399},   {712, 1101},
+    {44451, 44741}, {28660, 28946}, {40955, 41253}, {29424, 29864},
+    {14177, 14446}, {30219, 30632}, {24757, 25012}, {47991, 48306},
+    {42054, 42252}, {3984, 4419},   {42304, 42506}, {7160, 7543},
+    {2004, 2152},   {9777, 10105},  {15724, 16008}, {11263, 11573},
+    {15066, 15239}, {12108, 12336}, {17138, 17570}, {30472, 30714},
+    {41197, 41294}, {24294, 24496}, {17371, 17514}, {11426, 11749},
+    {25223, 25474}, {18083, 18345}, {27611, 27919}, {8116, 8261},
+    {40317, 40373}, {46652, 47026}, {18082, 18151}, {19808, 19970},
+    {46627, 46885}, {11646, 11789}, {1498, 1687},   {35907, 36081},
+    {36340, 36593}, {1255, 1311},   {43485, 43551}, {6586, 6895},
+    {10331, 10467}, {26803, 26998}, {14007, 14360}, {35951, 36120},
+    {37327, 37592}, {35419, 35724}, {50379, 50514}, {37251, 37489},
+    {27313, 27752}, {27502, 27845}, {36608, 36732}, {41751, 42057},
+    {19118, 19267}, {16529, 16926}, {49794, 50066}, {37378, 37699},
+    {7440, 7552},   {10418, 10650}, {50184, 50635}, {44350, 44579},
+    {8178, 8502},   {33838, 34017}, {11582, 11864}, {11756, 11785},
+    {42136, 42328}, {39404, 39545}, {13924, 14209}, {29411, 29627},
+    {10836, 11139}, {40332, 40598}, {26097, 26561}, {5422, 5512},
+    {30687, 30849}, {4399, 4726},   {50679, 50762}, {41224, 41439},
+    {46023, 46129}, {22690, 23010}, {37920, 38085}, {25885, 26249},
+    {51047, 51185}, {21508, 21904}, {6731, 7010},   {38144, 38493},
+    {47648, 47886}, {120, 603},     {49964, 50182}, {43503, 43765},
+    {24092, 24436}, {19204, 19509}, {19668, 19930}, {6815, 6963},
+    {10552, 10775}, {949, 1239},    {36976, 37348}, {34806, 34901},
+    {19939, 20308}, {42245, 42329}, {42700, 43067}, {13821, 14054},
+    {28109, 28331}, {32929, 33212}, {23736, 24036}, {31969, 32240},
+    {12326, 12612}, {5999, 6132},   {42871, 43283}, {33204, 33496},
+    {5757, 5991},   {46826, 46927}, {4994, 5278},   {47371, 47713},
+    {20886, 21106}, {38457, 38794}, {48451, 48789}, {34146, 34343},
+    {45911, 46248}, {48215, 48615}, {43970, 44131}, {30886, 31216},
+    {50135, 50292}, {3726, 3854},   {39041, 39408}, {48617, 48756},
+    {46205, 46590}, {39766, 39923}, {20835, 21106}, {43716, 44066},
+    {45665, 45789}, {12549, 12755}, {23366, 23752}, {17864, 17942},
+    {28288, 28528}, {2744, 2941},   {49355, 49605}, {34527, 34816},
+    {23092, 23447}, {5832, 5912},   {21146, 21478}, {30784, 30884},
+    {28221, 28469}, {34944, 35047}, {23956, 24126}, {7538, 7890},
+    {32496, 32803}, {16404, 16607}, {37968, 38277}, {7399, 7574},
+    {28605, 28842}, {50454, 50851}, {20581, 20845}, {21395, 21705},
+    {50726, 50871}, {11953, 12278}, {533, 822},     {5298, 5658},
+    {48707, 48914}, {21760, 22223}, {1889, 2146},   {6409, 6842},
+    {44094, 44473}, {18003, 18336}, {41550, 41926}, {50042, 50136},
+    {38646, 38835}, {5425, 5693},   {48967, 49383}, {376, 596},
+    {47514, 47704}, {43238, 43663}, {25440, 25655}, {25652, 26050},
+    {16909, 17232}, {41312, 41490}, {5909, 6049},   {3153, 3523},
+    {27877, 28046}, {26715, 26810}, {10031, 10108}, {32282, 32620},
+    {8934, 9219},   {5133, 5493},   {26666, 26787}, {45324, 45630},
+    {34880, 35008}, {20823, 20920}, {39571, 39704}, {15523, 15869},
+    {4360, 4637},   {46199, 46384}, {35991, 36242}, {46852, 46931},
+    {39218, 39644}, {11785, 12029}, {27225, 27366}, {29820, 30097},
+    {36778, 37072}, {9871, 10255},  {51065, 51208}, {38775, 39102},
+    {39446, 39712}, {33856, 34083}, {28853, 29289}, {526, 666},
+    {37510, 37697}, {13455, 13855}, {25648, 25691}, {10694, 11041},
+    {26441, 26889}, {18821, 19058}, {3357, 3590},   {15915, 16276},
+    {37706, 37934}, {24970, 25281}, {43951, 44124}, {35874, 36128},
+};
+
+void test_ngtcp2_rob_push_random(void) {
+  ngtcp2_mem *mem = ngtcp2_mem_default();
+  ngtcp2_rob rob;
+  int rv;
+  uint8_t data[512];
+  size_t i;
+
+  ngtcp2_rob_init(&rob, 1024 * 1024, mem);
+  for (i = 0; i < arraylen(randkeys); ++i) {
+    rv = ngtcp2_rob_push(&rob, randkeys[i].begin, &data[0],
+                         ngtcp2_range_len(&randkeys[i]));
+
+    CU_ASSERT(0 == rv);
+  }
+
+  CU_ASSERT(51401 == ngtcp2_rob_first_gap_offset(&rob));
 
   ngtcp2_rob_free(&rob);
 }
@@ -140,6 +286,8 @@ void test_ngtcp2_rob_data_at(void) {
   const uint8_t *p;
   size_t len;
   ngtcp2_rob_data *d;
+  ngtcp2_psl_it it;
+  ngtcp2_rob_gap *g;
 
   for (i = 0; i < sizeof(data); ++i) {
     data[i] = (uint8_t)i;
@@ -247,14 +395,20 @@ void test_ngtcp2_rob_data_at(void) {
 
   CU_ASSERT(0 == rv);
 
-  d = rob.data->next;
+  it = ngtcp2_psl_begin(&rob.datapsl);
+  ngtcp2_psl_it_next(&it);
+  d = ngtcp2_psl_it_get(&it);
+
+  CU_ASSERT(16 == d->range.begin);
 
-  CU_ASSERT(16 == d->offset);
+  ngtcp2_psl_it_next(&it);
+  d = ngtcp2_psl_it_get(&it);
 
-  d = d->next;
+  CU_ASSERT(32 == d->range.begin);
 
-  CU_ASSERT(32 == d->offset);
-  CU_ASSERT(NULL == d->next);
+  ngtcp2_psl_it_next(&it);
+
+  CU_ASSERT(ngtcp2_psl_it_end(&it));
 
   ngtcp2_rob_free(&rob);
 
@@ -281,8 +435,14 @@ void test_ngtcp2_rob_data_at(void) {
     ngtcp2_rob_pop(&rob, i * 16, len);
   }
 
-  CU_ASSERT(256 == rob.gap->range.begin);
-  CU_ASSERT(NULL == rob.data);
+  it = ngtcp2_psl_begin(&rob.gappsl);
+  g = ngtcp2_psl_it_get(&it);
+
+  CU_ASSERT(256 == g->range.begin);
+
+  it = ngtcp2_psl_begin(&rob.datapsl);
+
+  CU_ASSERT(ngtcp2_psl_it_end(&it));
 
   ngtcp2_rob_free(&rob);
 
@@ -340,6 +500,9 @@ void test_ngtcp2_rob_data_at(void) {
 void test_ngtcp2_rob_remove_prefix(void) {
   ngtcp2_mem *mem = ngtcp2_mem_default();
   ngtcp2_rob rob;
+  ngtcp2_rob_gap *g;
+  ngtcp2_rob_data *d;
+  ngtcp2_psl_it it;
   uint8_t data[256];
   int rv;
 
@@ -352,8 +515,15 @@ void test_ngtcp2_rob_remove_prefix(void) {
 
   ngtcp2_rob_remove_prefix(&rob, 33);
 
-  CU_ASSERT(33 == rob.gap->range.begin);
-  CU_ASSERT(32 == rob.data->offset);
+  it = ngtcp2_psl_begin(&rob.gappsl);
+  g = ngtcp2_psl_it_get(&it);
+
+  CU_ASSERT(33 == g->range.begin);
+
+  it = ngtcp2_psl_begin(&rob.datapsl);
+  d = ngtcp2_psl_it_get(&it);
+
+  CU_ASSERT(32 == d->range.begin);
 
   ngtcp2_rob_free(&rob);
 
@@ -370,8 +540,14 @@ void test_ngtcp2_rob_remove_prefix(void) {
 
   ngtcp2_rob_remove_prefix(&rob, 16);
 
-  CU_ASSERT(16 == rob.gap->range.begin);
-  CU_ASSERT(NULL == rob.gap->next);
+  it = ngtcp2_psl_begin(&rob.gappsl);
+  g = ngtcp2_psl_it_get(&it);
+
+  CU_ASSERT(16 == g->range.begin);
+
+  ngtcp2_psl_it_next(&it);
+
+  CU_ASSERT(ngtcp2_psl_it_end(&it));
 
   ngtcp2_rob_free(&rob);
 }
diff --git a/tests/ngtcp2_rob_test.h b/tests/ngtcp2_rob_test.h
index e59be5ac..3f7325aa 100644
--- a/tests/ngtcp2_rob_test.h
+++ b/tests/ngtcp2_rob_test.h
@@ -30,6 +30,7 @@
 #endif /* HAVE_CONFIG_H */
 
 void test_ngtcp2_rob_push(void);
+void test_ngtcp2_rob_push_random(void);
 void test_ngtcp2_rob_data_at(void);
 void test_ngtcp2_rob_remove_prefix(void);
 
-- 
GitLab