diff --git a/lib/includes/ngtcp2/ngtcp2.h b/lib/includes/ngtcp2/ngtcp2.h index 1c6d50c3834339a4ce09d4d3659ec2366872cb25..bafcaaf1a35eb734723b6fa5be2b2c389d07824f 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 88e99e762d2d81c783dd15ab09e182ef7f4585b2..0a090941b21e38ee76d64f9d3db74abfe72f1084 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 664001f1bccb69483458a73055f17d392324dbb1..456ea9fde0eceb2dabf5172bb0dc35c353583adf 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 acadcb44fe7ba49b7bc2bc4e84ee73ca9159fd97..5b90e30c303a405f5d2d86a489b31210dae7a72e 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 37140a9d5a7b92d38ba1cb10f923af4f826f96c3..433732900c87ed06b43c1c8701203ac0324a65d5 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 fa37fc0727ae6d5324c491f2d296f71b60f67028..fdb34aed4be381c0703ff823b8928703245ab4a5 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 7cf26b66afd998a3ca7beeabdc2ca864b33660ea..94609a81487bebcb40afd6a0075ed862b7f148c1 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 1efcde5ae8765dbedbd64d381c9aec509c4fac3c..09c52c8b5dfbbf0a1e3e8d2a3d87e100493c246b 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 e346e5303be66ffab14d0ae2e669e952d1639d7a..a75f013cd0f377b820a89a45f5ebcb11c2d056f5 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 b43d7357cc33d28536972246a254af6b2a1f27c6..20b87da95b21943ecb88675e4b31f3d86fcbb456 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 */