From 55bec839f15b142c609de8c6ba9e4e377827b4ac Mon Sep 17 00:00:00 2001
From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
Date: Sun, 9 Jul 2017 22:49:29 +0900
Subject: [PATCH] Re-write ack tracker

---
 lib/Makefile.am           |   4 +-
 lib/ngtcp2_acktr.c        |  82 +++++++++++++++++++++++++++++
 lib/ngtcp2_acktr.h        | 105 ++++++++++++++++++++++++++++++++++++++
 lib/ngtcp2_conn.c         |  84 ++++++++++--------------------
 lib/ngtcp2_conn.h         |  14 +----
 tests/Makefile.am         |   2 +
 tests/main.c              |   4 +-
 tests/ngtcp2_acktr_test.c |  82 +++++++++++++++++++++++++++++
 tests/ngtcp2_acktr_test.h |  34 ++++++++++++
 9 files changed, 340 insertions(+), 71 deletions(-)
 create mode 100644 lib/ngtcp2_acktr.c
 create mode 100644 lib/ngtcp2_acktr.h
 create mode 100644 tests/ngtcp2_acktr_test.c
 create mode 100644 tests/ngtcp2_acktr_test.h

diff --git a/lib/Makefile.am b/lib/Makefile.am
index d79cb3c8..2171f843 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -44,7 +44,8 @@ OBJECTS = \
 	ngtcp2_ppe.c \
 	ngtcp2_crypto.c \
 	ngtcp2_err.c \
-	ngtcp2_range.c
+	ngtcp2_range.c \
+	ngtcp2_acktr.c
 
 HFILES = \
 	ngtcp2_pkt.h \
@@ -60,6 +61,7 @@ HFILES = \
 	ngtcp2_crypto.h \
 	ngtcp2_err.h \
 	ngtcp2_range.h \
+	ngtcp2_acktr.h \
 	ngtcp2_macro.h
 
 libngtcp2_la_SOURCES = $(HFILES) $(OBJECTS)
diff --git a/lib/ngtcp2_acktr.c b/lib/ngtcp2_acktr.c
new file mode 100644
index 00000000..ac1897c8
--- /dev/null
+++ b/lib/ngtcp2_acktr.c
@@ -0,0 +1,82 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 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_acktr.h"
+
+int ngtcp2_acktr_entry_new(ngtcp2_acktr_entry **ent, uint64_t pkt_num,
+                           ngtcp2_tstamp tstamp, ngtcp2_mem *mem) {
+  *ent = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_acktr_entry));
+  if (*ent == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  (*ent)->next = NULL;
+  (*ent)->pkt_num = pkt_num;
+  (*ent)->tstamp = tstamp;
+
+  return 0;
+}
+
+void ngtcp2_acktr_entry_del(ngtcp2_acktr_entry *ent, ngtcp2_mem *mem) {
+  ngtcp2_mem_free(mem, ent);
+}
+
+void ngtcp2_acktr_init(ngtcp2_acktr *acktr) { acktr->ent = NULL; }
+
+void ngtcp2_acktr_free(ngtcp2_acktr *acktr) { (void)acktr; }
+
+int ngtcp2_acktr_add(ngtcp2_acktr *acktr, ngtcp2_acktr_entry *ent) {
+  ngtcp2_acktr_entry **pent;
+
+  for (pent = &acktr->ent; *pent; pent = &(*pent)->next) {
+    if ((*pent)->pkt_num > ent->pkt_num) {
+      continue;
+    }
+    /* TODO What to do if we receive duplicated packet number? */
+    if ((*pent)->pkt_num == ent->pkt_num) {
+      return NGTCP2_ERR_INVALID_ARGUMENT;
+    }
+    break;
+  }
+
+  ent->next = *pent;
+  *pent = ent;
+  return 0;
+}
+
+ngtcp2_acktr_entry *ngtcp2_acktr_get(ngtcp2_acktr *acktr) { return acktr->ent; }
+
+void ngtcp2_acktr_remove(ngtcp2_acktr *acktr, const ngtcp2_acktr_entry *ent) {
+  ngtcp2_acktr_entry **pent;
+
+  for (pent = &acktr->ent; *pent; pent = &(*pent)->next) {
+    if (ent->pkt_num != (*pent)->pkt_num) {
+      continue;
+    }
+
+    *pent = (*pent)->next;
+
+    return;
+  }
+}
diff --git a/lib/ngtcp2_acktr.h b/lib/ngtcp2_acktr.h
new file mode 100644
index 00000000..ae63f065
--- /dev/null
+++ b/lib/ngtcp2_acktr.h
@@ -0,0 +1,105 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 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_ACKTR_H
+#define NGTCP2_ACKTR_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_mem.h"
+
+struct ngtcp2_acktr_entry;
+typedef struct ngtcp2_acktr_entry ngtcp2_acktr_entry;
+
+/*
+ * ngtcp2_acktr_entry is a single packet which needs to be acked.
+ */
+struct ngtcp2_acktr_entry {
+  ngtcp2_acktr_entry *next;
+  uint64_t pkt_num;
+  ngtcp2_tstamp tstamp;
+};
+
+/*
+ * ngtcp2_acktr_entry_new allocates memory for ent, and initializes it
+ * with the given parameters.
+ */
+int ngtcp2_acktr_entry_new(ngtcp2_acktr_entry **ent, uint64_t pkt_num,
+                           ngtcp2_tstamp tstamp, ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_acktr_entry_del deallocates memory allocated for |ent|.  It
+ * deallocates memory pointed by |ent|.
+ */
+void ngtcp2_acktr_entry_del(ngtcp2_acktr_entry *ent, ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_acktr tracks received packets which we have to send ack.
+ */
+typedef struct {
+  /* ent points to the head of list which is ordered by the decreasing
+     order of packet number. */
+  ngtcp2_acktr_entry *ent;
+} ngtcp2_acktr;
+
+/*
+ * ngtcp2_acktr_init initializes |acktr|.
+ */
+void ngtcp2_acktr_init(ngtcp2_acktr *acktr);
+
+/*
+ * ngtcp2_acktr_free frees resources allocated for |acktr|.  It does
+ * not free any ngtcp2_acktr_entry directly or indirectly pointed by
+ * acktr->ent.
+ */
+void ngtcp2_acktr_free(ngtcp2_acktr *acktr);
+
+/*
+ * ngtcp2_acktr_add adds |ent|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     Same packet number has already been included in |acktr|.
+ */
+int ngtcp2_acktr_add(ngtcp2_acktr *acktr, ngtcp2_acktr_entry *ent);
+
+/*
+ * ngtcp2_acktr_get returns the entry which has the largest packet
+ * number to be acked.  If there is no entry, this function returns
+ * NULL.
+ */
+ngtcp2_acktr_entry *ngtcp2_acktr_get(ngtcp2_acktr *acktr);
+
+/*
+ * ngtcp2_acktr_remove removes the |ent|.
+ */
+void ngtcp2_acktr_remove(ngtcp2_acktr *acktr, const ngtcp2_acktr_entry *ent);
+
+#endif /* NGTCP2_ACKTR_H */
diff --git a/lib/ngtcp2_conn.c b/lib/ngtcp2_conn.c
index eaa4d0df..380576ba 100644
--- a/lib/ngtcp2_conn.c
+++ b/lib/ngtcp2_conn.c
@@ -109,15 +109,6 @@ static int conn_call_handshake_completed(ngtcp2_conn *conn) {
   return 0;
 }
 
-static int conn_ackq_greater(const void *lhsx, const void *rhsx) {
-  const ngtcp2_rx_pkt *lhs, *rhs;
-
-  lhs = ngtcp2_struct_of(lhsx, ngtcp2_rx_pkt, pq_entry);
-  rhs = ngtcp2_struct_of(rhsx, ngtcp2_rx_pkt, pq_entry);
-
-  return lhs->pkt_num > rhs->pkt_num;
-}
-
 static int conn_new(ngtcp2_conn **pconn, uint64_t conn_id, uint32_t version,
                     const ngtcp2_conn_callbacks *callbacks, void *user_data) {
   int rv;
@@ -129,16 +120,13 @@ static int conn_new(ngtcp2_conn **pconn, uint64_t conn_id, uint32_t version,
     goto fail_conn;
   }
 
-  rv = ngtcp2_pq_init(&(*pconn)->ackq, conn_ackq_greater, mem);
-  if (rv != 0) {
-    goto fail_pq_init;
-  }
-
   rv = ngtcp2_strm_init(&(*pconn)->strm0, mem);
   if (rv != 0) {
     goto fail_strm_init;
   }
 
+  ngtcp2_acktr_init(&(*pconn)->acktr);
+
   (*pconn)->callbacks = *callbacks;
   (*pconn)->conn_id = conn_id;
   (*pconn)->version = version;
@@ -148,8 +136,6 @@ static int conn_new(ngtcp2_conn **pconn, uint64_t conn_id, uint32_t version,
   return 0;
 
 fail_strm_init:
-  ngtcp2_pq_free(&(*pconn)->ackq);
-fail_pq_init:
   ngtcp2_mem_free(mem, *pconn);
 fail_conn:
   return rv;
@@ -188,16 +174,14 @@ int ngtcp2_conn_server_new(ngtcp2_conn **pconn, uint64_t conn_id,
   return 0;
 }
 
-static int ackq_rx_pkt_free(ngtcp2_pq_entry *item, void *arg) {
-  ngtcp2_rx_pkt *rpkt;
-  ngtcp2_conn *conn;
-
-  rpkt = ngtcp2_struct_of(item, ngtcp2_rx_pkt, pq_entry);
-  conn = arg;
+static void delete_acktr_entry(ngtcp2_acktr_entry *ent, ngtcp2_mem *mem) {
+  ngtcp2_acktr_entry *next;
 
-  ngtcp2_mem_free(conn->mem, rpkt);
-
-  return 0;
+  for (; ent;) {
+    next = ent->next;
+    ngtcp2_acktr_entry_del(ent, mem);
+    ent = next;
+  }
 }
 
 void ngtcp2_conn_del(ngtcp2_conn *conn) {
@@ -205,13 +189,14 @@ void ngtcp2_conn_del(ngtcp2_conn *conn) {
     return;
   }
 
+  delete_acktr_entry(conn->acktr.ent, conn->mem);
+  ngtcp2_acktr_free(&conn->acktr);
+
   ngtcp2_crypto_km_del(conn->rx_ckm, conn->mem);
   ngtcp2_crypto_km_del(conn->tx_ckm, conn->mem);
 
   ngtcp2_strm_free(&conn->strm0);
 
-  ngtcp2_pq_each(&conn->ackq, ackq_rx_pkt_free, conn);
-  ngtcp2_pq_free(&conn->ackq);
   ngtcp2_mem_free(conn->mem, conn);
 }
 
@@ -220,35 +205,31 @@ static int conn_create_ack_frame(ngtcp2_conn *conn, ngtcp2_ack *ack,
   uint64_t first_pkt_num;
   ngtcp2_tstamp ack_delay;
   uint64_t last_pkt_num;
-  ngtcp2_rx_pkt *rpkt;
   ngtcp2_ack_blk *blk;
   int initial = 1;
   uint64_t gap;
+  ngtcp2_acktr_entry *rpkt;
 
-  if (ngtcp2_pq_empty(&conn->ackq)) {
+  rpkt = ngtcp2_acktr_get(&conn->acktr);
+  if (rpkt == NULL) {
     return 0;
   }
 
-  rpkt = ngtcp2_struct_of(ngtcp2_pq_top(&conn->ackq), ngtcp2_rx_pkt, pq_entry);
-  ngtcp2_pq_pop(&conn->ackq);
-
   first_pkt_num = last_pkt_num = rpkt->pkt_num;
   ack_delay = ts - rpkt->tstamp;
 
-  ngtcp2_mem_free(conn->mem, rpkt);
+  ngtcp2_acktr_remove(&conn->acktr, rpkt);
+  ngtcp2_acktr_entry_del(rpkt, conn->mem);
 
   ack->type = NGTCP2_FRAME_ACK;
   ack->num_ts = 0;
   ack->num_blks = 0;
 
-  for (; !ngtcp2_pq_empty(&conn->ackq);) {
-    rpkt =
-        ngtcp2_struct_of(ngtcp2_pq_top(&conn->ackq), ngtcp2_rx_pkt, pq_entry);
-
+  for (; (rpkt = ngtcp2_acktr_get(&conn->acktr));) {
     if (rpkt->pkt_num + 1 == last_pkt_num) {
       last_pkt_num = rpkt->pkt_num;
-      ngtcp2_pq_pop(&conn->ackq);
-      ngtcp2_mem_free(conn->mem, rpkt);
+      ngtcp2_acktr_remove(&conn->acktr, rpkt);
+      ngtcp2_acktr_entry_del(rpkt, conn->mem);
       continue;
     }
 
@@ -274,8 +255,8 @@ static int conn_create_ack_frame(ngtcp2_conn *conn, ngtcp2_ack *ack,
 
     first_pkt_num = last_pkt_num = rpkt->pkt_num;
 
-    ngtcp2_pq_pop(&conn->ackq);
-    ngtcp2_mem_free(conn->mem, rpkt);
+    ngtcp2_acktr_remove(&conn->acktr, rpkt);
+    ngtcp2_acktr_entry_del(rpkt, conn->mem);
 
     if (ack->num_blks == 255) {
       break;
@@ -1026,30 +1007,19 @@ int ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, ngtcp2_stream *fr) {
   return ngtcp2_rob_push(&strm->rob, fr->offset, fr->data, fr->datalen);
 }
 
-/* TODO This is not efficient and not robust. */
 int ngtcp2_conn_sched_ack(ngtcp2_conn *conn, uint64_t pkt_num,
                           ngtcp2_tstamp ts) {
-  ngtcp2_rx_pkt *rpkt;
+  ngtcp2_acktr_entry *rpkt;
   int rv;
 
-  if (ngtcp2_pq_size(&conn->ackq) > 1024) {
-    return NGTCP2_ERR_INTERNAL;
-  }
-
-  rpkt = ngtcp2_mem_malloc(conn->mem, sizeof(ngtcp2_rx_pkt));
-  if (rpkt == NULL) {
-    return NGTCP2_ERR_NOMEM;
-  }
-
-  rpkt->pkt_num = pkt_num;
-  rpkt->tstamp = ts;
-
-  rv = ngtcp2_pq_push(&conn->ackq, &rpkt->pq_entry);
+  rv = ngtcp2_acktr_entry_new(&rpkt, pkt_num, ts, conn->mem);
   if (rv != 0) {
-    ngtcp2_mem_free(conn->mem, rpkt);
     return rv;
   }
 
+  /* TODO Ignore error for now */
+  ngtcp2_acktr_add(&conn->acktr, rpkt);
+
   return 0;
 }
 
diff --git a/lib/ngtcp2_conn.h b/lib/ngtcp2_conn.h
index 1e0e9df6..6ee1d891 100644
--- a/lib/ngtcp2_conn.h
+++ b/lib/ngtcp2_conn.h
@@ -35,7 +35,7 @@
 #include "ngtcp2_buf.h"
 #include "ngtcp2_rob.h"
 #include "ngtcp2_crypto.h"
-#include "ngtcp2_pq.h"
+#include "ngtcp2_acktr.h"
 
 typedef enum {
   /* Client specific handshake states */
@@ -83,19 +83,8 @@ uint64_t ngtcp2_strm_rx_offset(ngtcp2_strm *strm);
  */
 int ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, ngtcp2_stream *fr);
 
-/*
- * ngtcp2_rx_pkt records packet number and its reception timestamp for
- * its transmission of ACK.
- */
-typedef struct {
-  ngtcp2_pq_entry pq_entry;
-  uint64_t pkt_num;
-  ngtcp2_tstamp tstamp;
-} ngtcp2_rx_pkt;
-
 struct ngtcp2_conn {
   int state;
-  ngtcp2_pq ackq;
   ngtcp2_conn_callbacks callbacks;
   ngtcp2_strm strm0;
   uint64_t conn_id;
@@ -103,6 +92,7 @@ struct ngtcp2_conn {
   uint64_t max_rx_pkt_num;
   ngtcp2_mem *mem;
   void *user_data;
+  ngtcp2_acktr acktr;
   uint32_t version;
   int handshake_completed;
   int server;
diff --git a/tests/Makefile.am b/tests/Makefile.am
index ad111587..4252e56c 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -32,12 +32,14 @@ OBJECTS = \
 	ngtcp2_upe_test.c \
 	ngtcp2_range_test.c \
 	ngtcp2_rob_test.c \
+	ngtcp2_acktr_test.c \
 	ngtcp2_test_helper.c
 HFILES= \
 	ngtcp2_pkt_test.h \
 	ngtcp2_upe_test.h \
 	ngtcp2_range_test.h \
 	ngtcp2_rob_test.h \
+	ngtcp2_acktr_test.h \
 	ngtcp2_test_helper.h
 
 main_SOURCES = $(HFILES) $(OBJECTS)
diff --git a/tests/main.c b/tests/main.c
index 1962df3a..2af9d61d 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -35,6 +35,7 @@
 #include "ngtcp2_upe_test.h"
 #include "ngtcp2_range_test.h"
 #include "ngtcp2_rob_test.h"
+#include "ngtcp2_acktr_test.h"
 
 static int init_suite1(void) { return 0; }
 
@@ -83,7 +84,8 @@ int main() {
       !CU_add_test(pSuite, "rob_push", test_ngtcp2_rob_push) ||
       !CU_add_test(pSuite, "rob_data_at", test_ngtcp2_rob_data_at) ||
       !CU_add_test(pSuite, "rob_remove_prefix",
-                   test_ngtcp2_rob_remove_prefix)) {
+                   test_ngtcp2_rob_remove_prefix) ||
+      !CU_add_test(pSuite, "acktr_add", test_ngtcp2_acktr_add)) {
     CU_cleanup_registry();
     return CU_get_error();
   }
diff --git a/tests/ngtcp2_acktr_test.c b/tests/ngtcp2_acktr_test.c
new file mode 100644
index 00000000..03f88e47
--- /dev/null
+++ b/tests/ngtcp2_acktr_test.c
@@ -0,0 +1,82 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 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_acktr_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include "ngtcp2_acktr.h"
+#include "ngtcp2_test_helper.h"
+
+void test_ngtcp2_acktr_add(void) {
+  ngtcp2_acktr acktr;
+  ngtcp2_acktr_entry ents[] = {
+      {NULL, 1, 1000}, {NULL, 5, 1001}, {NULL, 7, 1002}, {NULL, 4, 1003},
+      {NULL, 6, 1004}, {NULL, 2, 1005}, {NULL, 3, 1006},
+  };
+  uint64_t max_pkt_num[] = {1, 5, 7, 7, 7, 7, 7};
+  ngtcp2_acktr_entry *ent;
+  size_t i;
+  int rv;
+
+  ngtcp2_acktr_init(&acktr);
+
+  for (i = 0; i < arraylen(ents); ++i) {
+    rv = ngtcp2_acktr_add(&acktr, &ents[i]);
+
+    CU_ASSERT(0 == rv);
+
+    ent = ngtcp2_acktr_get(&acktr);
+
+    CU_ASSERT(max_pkt_num[i] == ent->pkt_num);
+  }
+
+  for (i = 0; i < arraylen(ents); ++i) {
+    ent = ngtcp2_acktr_get(&acktr);
+    ngtcp2_acktr_remove(&acktr, ent);
+
+    ent = ngtcp2_acktr_get(&acktr);
+
+    if (i != arraylen(ents) - 1) {
+      CU_ASSERT(arraylen(ents) - i - 1 == ent->pkt_num);
+    } else {
+      CU_ASSERT(NULL == ent);
+    }
+  }
+
+  ngtcp2_acktr_free(&acktr);
+
+  /* Check duplicates */
+  ngtcp2_acktr_init(&acktr);
+
+  rv = ngtcp2_acktr_add(&acktr, &ents[0]);
+
+  CU_ASSERT(0 == rv);
+
+  rv = ngtcp2_acktr_add(&acktr, &ents[0]);
+
+  CU_ASSERT(NGTCP2_ERR_INVALID_ARGUMENT == rv);
+
+  ngtcp2_acktr_free(&acktr);
+}
diff --git a/tests/ngtcp2_acktr_test.h b/tests/ngtcp2_acktr_test.h
new file mode 100644
index 00000000..ed531aab
--- /dev/null
+++ b/tests/ngtcp2_acktr_test.h
@@ -0,0 +1,34 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 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_ACKTR_TEST_H
+#define NGTCP2_ACKTR_TEST_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_ngtcp2_acktr_add(void);
+
+#endif /* NGTCP2_ACKTR_TEST_H */
-- 
GitLab