From dde1301104e8a3523795c8f9c67d157d36ffd28c Mon Sep 17 00:00:00 2001
From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
Date: Sun, 18 Jun 2017 17:25:32 +0900
Subject: [PATCH] Add ACK frame parser

At the moment, it just extracts Largest Acknowledged.  It is rather
implemented as place holder to parse incoming ACK frames without
error.
---
 lib/includes/ngtcp2/ngtcp2.h |  6 +++
 lib/ngtcp2_conv.c            | 11 ++++
 lib/ngtcp2_conv.h            | 14 ++++++
 lib/ngtcp2_pkt.c             | 98 ++++++++++++++++++++++++++++++++++--
 lib/ngtcp2_pkt.h             | 46 ++++++++++++-----
 tests/main.c                 |  4 +-
 tests/ngtcp2_pkt_test.c      | 20 ++++++++
 tests/ngtcp2_pkt_test.h      |  1 +
 tests/ngtcp2_test_helper.c   | 17 +++++++
 tests/ngtcp2_test_helper.h   | 10 ++++
 10 files changed, 208 insertions(+), 19 deletions(-)

diff --git a/lib/includes/ngtcp2/ngtcp2.h b/lib/includes/ngtcp2/ngtcp2.h
index 1c6d50c3..bafcaaf1 100644
--- a/lib/includes/ngtcp2/ngtcp2.h
+++ b/lib/includes/ngtcp2/ngtcp2.h
@@ -137,9 +137,15 @@ typedef struct {
   const uint8_t *data;
 } ngtcp2_stream;
 
+typedef struct {
+  uint8_t type;
+  uint64_t largest_ack;
+} ngtcp2_ack;
+
 typedef union {
   uint8_t type;
   ngtcp2_stream stream;
+  ngtcp2_ack ack;
 } ngtcp2_frame;
 
 /**
diff --git a/lib/ngtcp2_conv.c b/lib/ngtcp2_conv.c
index 88e99e76..0a090941 100644
--- a/lib/ngtcp2_conv.c
+++ b/lib/ngtcp2_conv.c
@@ -45,6 +45,12 @@ uint64_t ngtcp2_get_uint64(const uint8_t *p) {
   return bswap64(n);
 }
 
+uint64_t ngtcp2_get_uint48(const uint8_t *p) {
+  uint64_t n = 0;
+  memcpy(((uint8_t *)&n) + 2, p, 6);
+  return bswap64(n);
+}
+
 uint32_t ngtcp2_get_uint32(const uint8_t *p) {
   uint32_t n;
   memcpy(&n, p, 4);
@@ -68,6 +74,11 @@ uint8_t *ngtcp2_put_uint64be(uint8_t *p, uint64_t n) {
   return ngtcp2_cpymem(p, (const uint8_t *)&n, sizeof(n));
 }
 
+uint8_t *ngtcp2_put_uint48be(uint8_t *p, uint64_t n) {
+  n = bswap64(n);
+  return ngtcp2_cpymem(p, ((const uint8_t *)&n) + 2, 6);
+}
+
 uint8_t *ngtcp2_put_uint32be(uint8_t *p, uint32_t n) {
   n = htonl(n);
   return ngtcp2_cpymem(p, (const uint8_t *)&n, sizeof(n));
diff --git a/lib/ngtcp2_conv.h b/lib/ngtcp2_conv.h
index 664001f1..456ea9fd 100644
--- a/lib/ngtcp2_conv.h
+++ b/lib/ngtcp2_conv.h
@@ -38,6 +38,13 @@
  */
 uint64_t ngtcp2_get_uint64(const uint8_t *p);
 
+/*
+ * ngtcp2_get_uint48 reads 6 bytes from |p| as 48 bits unsigned
+ * integer encoded as network byte order, and returns it in host byte
+ * order.
+ */
+uint64_t ngtcp2_get_uint48(const uint8_t *p);
+
 /*
  * ngtcp2_get_uint32 reads 4 bytes from |p| as 32 bits unsigned
  * integer encoded as network byte order, and returns it in host byte
@@ -66,6 +73,13 @@ uint16_t ngtcp2_get_uint16(const uint8_t *p);
  */
 uint8_t *ngtcp2_put_uint64be(uint8_t *p, uint64_t n);
 
+/*
+ * ngtcp2_put_uint48be writes |n| in host byte order in |p| in network
+ * byte order.  It writes only least significant 48 bits.  It returns
+ * the one beyond of the last written position.
+ */
+uint8_t *ngtcp2_put_uint48be(uint8_t *p, uint64_t n);
+
 /*
  * ngtcp2_put_uint32be writes |n| in host byte order in |p| in network
  * byte order.  It returns the one beyond of the last written
diff --git a/lib/ngtcp2_pkt.c b/lib/ngtcp2_pkt.c
index acadcb44..5b90e30c 100644
--- a/lib/ngtcp2_pkt.c
+++ b/lib/ngtcp2_pkt.c
@@ -389,11 +389,99 @@ ssize_t ngtcp2_pkt_decode_stream_frame(ngtcp2_frame *dest,
 }
 
 ssize_t ngtcp2_pkt_decode_ack_frame(ngtcp2_frame *dest, const uint8_t *payload,
-                                    size_t len) {
-  (void)dest;
-  (void)payload;
-  (void)len;
-  return -1;
+                                    size_t payloadlen) {
+  uint8_t type;
+  size_t num_blks = 0;
+  size_t num_ts;
+  size_t lalen;
+  size_t abllen;
+  size_t len = 4;
+  const uint8_t *p;
+
+  /* We can expect at least 3 bytes (type, NumTS, and LA) */
+  if (payloadlen < 3 || !has_mask(payload[0], NGTCP2_FRAME_ACK)) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  p = &payload[0];
+
+  type = *p++;
+
+  if (type & NGTCP2_ACK_N_BIT) {
+    num_blks = *p++;
+    ++len;
+  }
+
+  num_ts = *p++;
+
+  switch ((type & NGTCP2_ACK_LL_MASK) >> 2) {
+  case 0x00:
+    lalen = 1;
+    break;
+  case 0x01:
+    lalen = 2;
+    break;
+  case 0x02:
+    lalen = 4;
+    break;
+  case 0x03:
+    lalen = 6;
+    break;
+  }
+
+  len += lalen;
+
+  switch (type & NGTCP2_ACK_MM_MASK) {
+  case 0x00:
+    abllen = 1;
+    break;
+  case 0x01:
+    abllen = 2;
+    break;
+  case 0x02:
+    abllen = 4;
+    break;
+  case 0x03:
+    abllen = 6;
+    break;
+  }
+
+  /* Length of ACK Block Section */
+  /* First ACK Block Length */
+  len += lalen;
+  len += num_blks * abllen;
+
+  /* Length of Timestamp Section */
+  if (num_ts > 0) {
+    len += num_ts * 3 + 2;
+  }
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  dest->type = NGTCP2_FRAME_ACK;
+
+  switch (lalen) {
+  case 1:
+    dest->ack.largest_ack = *p;
+    break;
+  case 2:
+    dest->ack.largest_ack = ngtcp2_get_uint16(p);
+    break;
+  case 4:
+    dest->ack.largest_ack = ngtcp2_get_uint32(p);
+    break;
+  case 6:
+    dest->ack.largest_ack = ngtcp2_get_uint48(p);
+    break;
+  }
+
+  p += lalen;
+
+  /* TODO Parse remaining fields */
+
+  return (ssize_t)len;
 }
 
 ssize_t ngtcp2_pkt_decode_padding_frame(ngtcp2_frame *dest,
diff --git a/lib/ngtcp2_pkt.h b/lib/ngtcp2_pkt.h
index 37140a9d..43373290 100644
--- a/lib/ngtcp2_pkt.h
+++ b/lib/ngtcp2_pkt.h
@@ -45,6 +45,10 @@
 #define NGTCP2_STREAM_OO_MASK 0x06
 #define NGTCP2_STREAM_D_BIT 0x01
 
+#define NGTCP2_ACK_N_BIT 0x10
+#define NGTCP2_ACK_LL_MASK 0x0c
+#define NGTCP2_ACK_MM_MASK 0x03
+
 /*
  * ngtcp2_pkt_hd_init initializes |hd| with the given values.
  */
@@ -120,49 +124,65 @@ ssize_t ngtcp2_pkt_decode_stream_frame(ngtcp2_frame *dest,
                                        const uint8_t *payload,
                                        size_t payloadlen);
 
+/*
+ * ngtcp2_pkt_decode_ack_frame decodes ACK frame from |payload| of
+ * length |payloadlen|.  The result is stored in the object pointed by
+ * |dest|.  ACK frame must start at `payload[0]`.  This function
+ * returns when it decodes one ACK frame, and returns the exact number
+ * of bytes for one ACK frame if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_INVALID_ARGUMENT Type indicates that payload does not
+ *     include ACK frame; or Payload is too short to include ACK frame
+ */
 ssize_t ngtcp2_pkt_decode_ack_frame(ngtcp2_frame *dest, const uint8_t *payload,
-                                    size_t len);
+                                    size_t payloadlen);
 
 ssize_t ngtcp2_pkt_decode_padding_frame(ngtcp2_frame *dest,
-                                        const uint8_t *payload, size_t len);
+                                        const uint8_t *payload,
+                                        size_t payloadlen);
 
 ssize_t ngtcp2_pkt_decode_rst_stream_frame(ngtcp2_frame *dest,
-                                           const uint8_t *payload, size_t len);
+                                           const uint8_t *payload,
+                                           size_t payloadlen);
 
 ssize_t ngtcp2_pkt_decode_connection_close_frame(ngtcp2_frame *dest,
                                                  const uint8_t *payload,
-                                                 size_t len);
+                                                 size_t payloadlen);
 
 ssize_t ngtcp2_pkt_decode_goaway_frame(ngtcp2_frame *dest,
-                                       const uint8_t *payload, size_t len);
+                                       const uint8_t *payload,
+                                       size_t payloadlen);
 
 ssize_t ngtcp2_pkt_decode_max_data_frame(ngtcp2_frame *dest,
-                                         const uint8_t *payload, size_t len);
+                                         const uint8_t *payload,
+                                         size_t payloadlen);
 
 ssize_t ngtcp2_pkt_decode_max_stream_data_frame(ngtcp2_frame *dest,
                                                 const uint8_t *payload,
-                                                size_t len);
+                                                size_t payloadlen);
 
 ssize_t ngtcp2_pkt_decode_max_stream_id_frame(ngtcp2_frame *dest,
                                               const uint8_t *payload,
-                                              size_t len);
+                                              size_t payloadlen);
 
 ssize_t ngtcp2_pkt_decode_ping_frame(ngtcp2_frame *dest, const uint8_t *payload,
-                                     size_t len);
+                                     size_t payloadlen);
 
 ssize_t ngtcp2_pkt_decode_blocked_frame(ngtcp2_frame *dest,
-                                        const uint8_t *payload, size_t len);
+                                        const uint8_t *payload,
+                                        size_t payloadlen);
 
 ssize_t ngtcp2_pkt_decode_stream_blocked_frame(ngtcp2_frame *dest,
                                                const uint8_t *payload,
-                                               size_t len);
+                                               size_t payloadlen);
 
 ssize_t ngtcp2_pkt_decode_stream_id_needed_frame(ngtcp2_frame *dest,
                                                  const uint8_t *payload,
-                                                 size_t len);
+                                                 size_t payloadlen);
 
 ssize_t ngtcp2_pkt_decode_new_connection_id_frame(ngtcp2_frame *dest,
                                                   const uint8_t *payload,
-                                                  size_t len);
+                                                  size_t payloadlen);
 
 #endif /* NGTCP2_PKT_H */
diff --git a/tests/main.c b/tests/main.c
index fa37fc07..fdb34aed 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -58,7 +58,9 @@ int main() {
       !CU_add_test(pSuite, "pkt_decode_hd_short",
                    test_ngtcp2_pkt_decode_hd_short) ||
       !CU_add_test(pSuite, "pkt_decode_stream_frame",
-                   test_ngtcp2_pkt_decode_stream_frame)) {
+                   test_ngtcp2_pkt_decode_stream_frame) ||
+      !CU_add_test(pSuite, "pkt_decode_ack_frame",
+                   test_ngtcp2_pkt_decode_ack_frame)) {
     CU_cleanup_registry();
     return CU_get_error();
   }
diff --git a/tests/ngtcp2_pkt_test.c b/tests/ngtcp2_pkt_test.c
index 7cf26b66..94609a81 100644
--- a/tests/ngtcp2_pkt_test.c
+++ b/tests/ngtcp2_pkt_test.c
@@ -258,3 +258,23 @@ void test_ngtcp2_pkt_decode_stream_frame(void) {
 
   memset(&fm, 0, sizeof(fm));
 }
+
+void test_ngtcp2_pkt_decode_ack_frame(void) {
+  uint8_t buf[256];
+  size_t buflen;
+  ngtcp2_frame fm;
+  ssize_t rv;
+  size_t expectedlen;
+
+  /* 48 bits Largest Acknowledged + No Num Blocks + 0 NumTS*/
+  buflen = ngtcp2_t_encode_ack_frame(buf, 0xf1f2f3f4f5f6llu);
+
+  expectedlen = 1 + 1 + 6 + 2 + 6;
+
+  CU_ASSERT(expectedlen == buflen);
+
+  rv = ngtcp2_pkt_decode_ack_frame(&fm, buf, buflen);
+
+  CU_ASSERT((ssize_t)expectedlen == rv);
+  CU_ASSERT(0xf1f2f3f4f5f6llu == fm.ack.largest_ack);
+}
diff --git a/tests/ngtcp2_pkt_test.h b/tests/ngtcp2_pkt_test.h
index 1efcde5a..09c52c8b 100644
--- a/tests/ngtcp2_pkt_test.h
+++ b/tests/ngtcp2_pkt_test.h
@@ -32,5 +32,6 @@
 void test_ngtcp2_pkt_decode_hd_long(void);
 void test_ngtcp2_pkt_decode_hd_short(void);
 void test_ngtcp2_pkt_decode_stream_frame(void);
+void test_ngtcp2_pkt_decode_ack_frame(void);
 
 #endif /* NGTCP2_PKT_TEST_H */
diff --git a/tests/ngtcp2_test_helper.c b/tests/ngtcp2_test_helper.c
index e346e530..a75f013c 100644
--- a/tests/ngtcp2_test_helper.c
+++ b/tests/ngtcp2_test_helper.c
@@ -69,3 +69,20 @@ size_t ngtcp2_t_encode_stream_frame(uint8_t *out, uint8_t flags,
 
   return (size_t)(p - out);
 }
+
+size_t ngtcp2_t_encode_ack_frame(uint8_t *out, uint64_t largest_ack) {
+  uint8_t *p = out;
+
+  p = out;
+
+  *p++ = 0x0c | NGTCP2_FRAME_ACK;
+  /* NumTS */
+  *p++ = 0;
+  p = ngtcp2_put_uint48be(p, largest_ack);
+  p = ngtcp2_put_uint16be(p, 0);
+  /* TODO Draft-04 is pretty much ambiguous that whether the length of
+     First ACK Block Length is subject to LL or MM mask. */
+  p = ngtcp2_put_uint48be(p, 0);
+
+  return (size_t)(p - out);
+}
diff --git a/tests/ngtcp2_test_helper.h b/tests/ngtcp2_test_helper.h
index b43d7357..20b87da9 100644
--- a/tests/ngtcp2_test_helper.h
+++ b/tests/ngtcp2_test_helper.h
@@ -45,4 +45,14 @@ size_t ngtcp2_t_encode_stream_frame(uint8_t *out, uint8_t flags,
                                     uint32_t stream_id, uint64_t offset,
                                     uint16_t datalen);
 
+/*
+ * ngtcp2_t_encode_ack_frame encodes ACK frame into |out| with the
+ * given parameters.  Currently, this function encodes |largest_ack|
+ * in 48 bits, and omits Num Blocks field.  NumTS and ACK Delay fields
+ * are always 0.
+ *
+ * This function returns the number of bytes written to |out|.
+ */
+size_t ngtcp2_t_encode_ack_frame(uint8_t *out, uint64_t largest_ack);
+
 #endif /* NGTCP2_TEST_HELPER_H */
-- 
GitLab