From aed626bfa5c934d5b690625455aee5b02fa04927 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 28 Jan 2012 19:22:38 +0900 Subject: [PATCH] Added GOAWAY handling --- lib/includes/spdylay/spdylay.h | 7 +++ lib/spdylay_frame.c | 41 ++++++++++++++ lib/spdylay_frame.h | 21 ++++++++ lib/spdylay_session.c | 99 ++++++++++++++++++++++++++++++++-- lib/spdylay_session.h | 23 ++++++++ tests/main.c | 4 ++ tests/spdylay_frame_test.c | 22 ++++++++ tests/spdylay_frame_test.h | 1 + tests/spdylay_session_test.c | 31 +++++++++++ tests/spdylay_session_test.h | 1 + 10 files changed, 247 insertions(+), 3 deletions(-) diff --git a/lib/includes/spdylay/spdylay.h b/lib/includes/spdylay/spdylay.h index 0e7617ac..337a45bd 100644 --- a/lib/includes/spdylay/spdylay.h +++ b/lib/includes/spdylay/spdylay.h @@ -125,6 +125,12 @@ typedef struct { uint32_t unique_id; } spdylay_ping; +typedef struct { + spdylay_ctrl_hd hd; + int32_t last_good_stream_id; + uint32_t status_code; +} spdylay_goaway; + typedef union { int fd; void *ptr; @@ -150,6 +156,7 @@ typedef union { spdylay_syn_reply syn_reply; spdylay_rst_stream rst_stream; spdylay_ping ping; + spdylay_goaway goaway; spdylay_headers headers; spdylay_data data; } spdylay_frame; diff --git a/lib/spdylay_frame.c b/lib/spdylay_frame.c index ea58590c..8f613df2 100644 --- a/lib/spdylay_frame.c +++ b/lib/spdylay_frame.c @@ -399,6 +399,19 @@ void spdylay_frame_ping_init(spdylay_ping *frame, uint32_t unique_id) void spdylay_frame_ping_free(spdylay_ping *frame) {} +void spdylay_frame_goaway_init(spdylay_goaway *frame, + int32_t last_good_stream_id) +{ + memset(frame, 0, sizeof(spdylay_goaway)); + frame->hd.version = SPDYLAY_PROTO_VERSION; + frame->hd.type = SPDYLAY_GOAWAY; + frame->hd.length = 4; + frame->last_good_stream_id = last_good_stream_id; +} + +void spdylay_frame_goaway_free(spdylay_goaway *frame) +{} + void spdylay_frame_headers_init(spdylay_headers *frame, uint8_t flags, int32_t stream_id, char **nv) { @@ -551,6 +564,34 @@ int spdylay_frame_unpack_ping(spdylay_ping *frame, return 0; } +ssize_t spdylay_frame_pack_goaway(uint8_t **buf_ptr, spdylay_goaway *frame) +{ + uint8_t *framebuf = NULL; + ssize_t framelen = 12; + framebuf = malloc(framelen); + if(framebuf == NULL) { + return SPDYLAY_ERR_NOMEM; + } + memset(framebuf, 0, framelen); + spdylay_frame_pack_ctrl_hd(framebuf, &frame->hd); + spdylay_put_uint32be(&framebuf[8], frame->last_good_stream_id); + *buf_ptr = framebuf; + return framelen; +} + +int spdylay_frame_unpack_goaway(spdylay_goaway *frame, + const uint8_t *head, size_t headlen, + const uint8_t *payload, size_t payloadlen) +{ + if(payloadlen < 4) { + return SPDYLAY_ERR_INVALID_FRAME; + } + spdylay_frame_unpack_ctrl_hd(&frame->hd, head); + frame->last_good_stream_id = spdylay_get_uint32(payload) & + SPDYLAY_STREAM_ID_MASK; + return 0; +} + #define SPDYLAY_HEADERS_NV_OFFSET 14 ssize_t spdylay_frame_pack_headers(uint8_t **buf_ptr, diff --git a/lib/spdylay_frame.h b/lib/spdylay_frame.h index 3e0bc940..5e4e4eb4 100644 --- a/lib/spdylay_frame.h +++ b/lib/spdylay_frame.h @@ -101,6 +101,22 @@ int spdylay_frame_unpack_ping(spdylay_ping *frame, const uint8_t *head, size_t headlen, const uint8_t *payload, size_t payloadlen); +/* + * Packs GOAWAY frame |frame | in wire format and store it in + * |*buf_ptr|. This function allocates enough memory in |*buf_ptr| to + * store given |frame|. This function returns the size of packed frame + * if it succeeds, or negative error code. + */ +ssize_t spdylay_frame_pack_goaway(uint8_t **buf_ptr, spdylay_goaway *frame); + +/* + * Unpacks GOAWAY wire format into |frame|. This function returns 0 if + * it succeeds, or negative error code. + */ +int spdylay_frame_unpack_goaway(spdylay_goaway *frame, + const uint8_t *head, size_t headlen, + const uint8_t *payload, size_t payloadlen); + /* * Packs HEADERS frame |frame| in wire format and store it in * |*buf_ptr|. This function allocates enough memory in |*buf_ptr| to @@ -181,6 +197,11 @@ void spdylay_frame_ping_init(spdylay_ping *frame, uint32_t unique_id); void spdylay_frame_ping_free(spdylay_ping *frame); +void spdylay_frame_goaway_init(spdylay_goaway *frame, + int32_t last_good_stream_id); + +void spdylay_frame_goaway_free(spdylay_goaway *frame); + void spdylay_frame_headers_init(spdylay_headers *frame, uint8_t flags, int32_t stream_id, char **nv); diff --git a/lib/spdylay_session.c b/lib/spdylay_session.c index 467fbd45..1cfe1e00 100644 --- a/lib/spdylay_session.c +++ b/lib/spdylay_session.c @@ -80,6 +80,9 @@ int spdylay_session_client_new(spdylay_session **session_ptr, (*session_ptr)->last_ping_unique_id = 0; memset(&(*session_ptr)->last_ping_time, 0, sizeof(struct timespec)); + (*session_ptr)->goaway_flags = SPDYLAY_GOAWAY_NONE; + (*session_ptr)->last_good_stream_id = 0; + r = spdylay_zlib_deflate_hd_init(&(*session_ptr)->hd_deflater); if(r != 0) { free(*session_ptr); @@ -144,6 +147,9 @@ static void spdylay_outbound_item_free(spdylay_outbound_item *item) case SPDYLAY_PING: spdylay_frame_ping_free(&item->frame->ping); break; + case SPDYLAY_GOAWAY: + spdylay_frame_goaway_free(&item->frame->goaway); + break; case SPDYLAY_HEADERS: spdylay_frame_headers_free(&item->frame->headers); break; @@ -215,6 +221,9 @@ int spdylay_session_add_frame(spdylay_session *session, /* Ping has "height" priority. Give it -1. */ item->pri = -1; break; + case SPDYLAY_GOAWAY: + /* Should GOAWAY have higher priority? */ + break; case SPDYLAY_HEADERS: /* Currently we don't have any API to send HEADERS frame, so this is unreachable. */ @@ -328,6 +337,11 @@ ssize_t spdylay_session_prep_frame(spdylay_session *session, int r; switch(item->frame_type) { case SPDYLAY_SYN_STREAM: { + if(session->goaway_flags) { + /* When GOAWAY is sent or received, peer must not send new + SYN_STREAM. */ + return SPDYLAY_ERR_INVALID_FRAME; + } item->frame->syn_stream.stream_id = session->next_stream_id; session->next_stream_id += 2; framebuflen = spdylay_frame_pack_syn_stream(&framebuf, @@ -373,6 +387,19 @@ ssize_t spdylay_session_prep_frame(spdylay_session *session, /* Currently we don't have any API to send HEADERS frame, so this is unreachable. */ abort(); + case SPDYLAY_GOAWAY: + if(session->goaway_flags & SPDYLAY_GOAWAY_SEND) { + /* TODO The spec does not mandate that both endpoints have to + exchange GOAWAY. This implementation allows receiver of first + GOAWAY can sent its own GOAWAY to tell the remote peer that + last-good-stream-id. */ + return SPDYLAY_ERR_INVALID_FRAME; + } + framebuflen = spdylay_frame_pack_goaway(&framebuf, &item->frame->goaway); + if(framebuflen < 0) { + return framebuflen; + } + break; case SPDYLAY_DATA: { if(!spdylay_session_is_data_allowed(session, item->frame->data.stream_id)) { return SPDYLAY_ERR_INVALID_FRAME; @@ -454,6 +481,9 @@ static int spdylay_session_after_frame_sent(spdylay_session *session) /* TODO If clock_gettime() fails, what should we do? */ clock_gettime(CLOCK_MONOTONIC, &session->last_ping_time); break; + case SPDYLAY_GOAWAY: + session->goaway_flags |= SPDYLAY_GOAWAY_SEND; + break; case SPDYLAY_HEADERS: /* Currently we don't have any API to send HEADERS frame, so this is unreachable. */ @@ -530,7 +560,11 @@ int spdylay_session_send(spdylay_session *session) /* TODO Call error callback? */ spdylay_outbound_item_free(item); free(item); - continue;; + if(framebuflen <= SPDYLAY_ERR_FATAL) { + return framebuflen; + } else { + continue;; + } } session->aob.item = item; session->aob.framebuf = framebuf; @@ -686,6 +720,10 @@ int spdylay_session_on_syn_stream_received(spdylay_session *session, spdylay_frame *frame) { int r; + if(session->goaway_flags) { + /* We don't accept SYN_STREAM after GOAWAY is sent or received. */ + return 0; + } if(spdylay_session_validate_syn_stream(session, &frame->syn_stream) == 0) { uint8_t flags = frame->syn_stream.hd.flags; if((flags & SPDYLAY_FLAG_FIN) && (flags & SPDYLAY_FLAG_UNIDIRECTIONAL)) { @@ -786,6 +824,25 @@ int spdylay_session_on_ping_received(spdylay_session *session, return r; } +int spdylay_session_on_goaway_received(spdylay_session *session, + spdylay_frame *frame) +{ + int r; + session->last_good_stream_id = frame->goaway.last_good_stream_id; + session->goaway_flags |= SPDYLAY_GOAWAY_RECV; + if(!(session->goaway_flags & SPDYLAY_GOAWAY_SEND)) { + /* TODO The spec does not mandate to send back GOAWAY. I think the + remote endpoint does not expect this, but sending GOAWAY does + not harm. */ + r = spdylay_session_add_goaway(session, session->last_recv_stream_id); + if(r != 0) { + return r; + } + } + spdylay_session_call_on_ctrl_frame_received(session, SPDYLAY_GOAWAY, frame); + return 0; +} + int spdylay_session_on_headers_received(spdylay_session *session, spdylay_frame *frame) { @@ -895,6 +952,17 @@ int spdylay_session_process_ctrl_frame(spdylay_session *session) spdylay_frame_ping_free(&frame.ping); } break; + case SPDYLAY_GOAWAY: + r = spdylay_frame_unpack_goaway(&frame.goaway, + session->iframe.headbuf, + sizeof(session->iframe.headbuf), + session->iframe.buf, + session->iframe.len); + if(r == 0) { + r = spdylay_session_on_goaway_received(session, &frame); + spdylay_frame_goaway_free(&frame.goaway); + } + break; case SPDYLAY_HEADERS: r = spdylay_frame_unpack_headers(&frame.headers, session->iframe.headbuf, @@ -1025,12 +1093,19 @@ int spdylay_session_recv(spdylay_session *session) int spdylay_session_want_read(spdylay_session *session) { - return 1; + /* If GOAWAY is not sent or received, we always want to read + incoming frames. After GOAWAY is sent or received, we are only + interested in existing streams. */ + return !(session->goaway_flags & SPDYLAY_GOAWAY_SEND) || + spdylay_map_size(&session->streams) == 0; } int spdylay_session_want_write(spdylay_session *session) { - return session->aob.item != NULL || !spdylay_pq_empty(&session->ob_pq); + uint8_t goaway_sent = session->goaway_flags & SPDYLAY_GOAWAY_SEND; + return (!goaway_sent && + (session->aob.item != NULL || !spdylay_pq_empty(&session->ob_pq))) || + (goaway_sent && spdylay_map_size(&session->streams) == 0); } int spdylay_session_add_ping(spdylay_session *session, uint32_t unique_id) @@ -1050,6 +1125,24 @@ int spdylay_session_add_ping(spdylay_session *session, uint32_t unique_id) return r; } +int spdylay_session_add_goaway(spdylay_session *session, + int32_t last_good_stream_id) +{ + int r; + spdylay_frame *frame; + frame = malloc(sizeof(spdylay_frame)); + if(frame == NULL) { + return SPDYLAY_ERR_NOMEM; + } + spdylay_frame_goaway_init(&frame->goaway, last_good_stream_id); + r = spdylay_session_add_frame(session, SPDYLAY_GOAWAY, frame); + if(r != 0) { + spdylay_frame_goaway_free(&frame->goaway); + free(frame); + } + return r; +} + int spdylay_submit_ping(spdylay_session *session) { return spdylay_session_add_ping(session, diff --git a/lib/spdylay_session.h b/lib/spdylay_session.h index 4298f756..454eac52 100644 --- a/lib/spdylay_session.h +++ b/lib/spdylay_session.h @@ -79,6 +79,14 @@ typedef struct { uint8_t ign; } spdylay_inbound_frame; +typedef enum { + SPDYLAY_GOAWAY_NONE = 0, + /* Flag means GOAWAY frame is sent to the remote peer. */ + SPDYLAY_GOAWAY_SEND = 0x1, + /* Flag means GOAWAY frame is received from the remote peer. */ + SPDYLAY_GOAWAY_RECV = 0x2 +} spdylay_goaway_flag; + typedef struct spdylay_session { uint8_t server; int32_t next_stream_id; @@ -103,6 +111,12 @@ typedef struct spdylay_session { /* Time stamp when last ping is sent. */ struct timespec last_ping_time; + /* Flags indicating GOAWAY is sent and/or recieved. The flags are + composed by bitwise OR-ing spdylay_goaway_flag. */ + uint8_t goaway_flags; + /* This is the value in GOAWAY frame sent by remote endpoint. */ + int32_t last_good_stream_id; + spdylay_session_callbacks callbacks; void *user_data; } spdylay_session; @@ -118,6 +132,9 @@ int spdylay_session_add_rst_stream(spdylay_session *session, int spdylay_session_add_ping(spdylay_session *session, uint32_t unique_id); +int spdylay_session_add_goaway(spdylay_session *session, + int32_t last_good_stream_id); + /* * Creates new stream in |session| with stream ID |stream_id|, * priority |pri| and flags |flags|. Currently, |flags| & @@ -167,6 +184,12 @@ int spdylay_session_on_rst_stream_received(spdylay_session *session, int spdylay_session_on_ping_received(spdylay_session *session, spdylay_frame *frame); +/* + * Called when GOAWAY is received. Received frame is |frame|. + */ +int spdylay_session_on_goaway_received(spdylay_session *session, + spdylay_frame *frame); + /* * Called when HEADERS is recieved. Received frame is |frame|. */ diff --git a/tests/main.c b/tests/main.c index dc09f970..d480f07b 100644 --- a/tests/main.c +++ b/tests/main.c @@ -86,10 +86,14 @@ int main() test_spdylay_session_on_headers_received) || !CU_add_test(pSuite, "session_on_ping_received", test_spdylay_session_on_ping_received) || + !CU_add_test(pSuite, "session_on_goaway_received", + test_spdylay_session_on_goaway_received) || !CU_add_test(pSuite, "frame_unpack_nv", test_spdylay_frame_unpack_nv) || !CU_add_test(pSuite, "frame_count_nv_space", test_spdylay_frame_count_nv_space) || !CU_add_test(pSuite, "frame_pack_ping", test_spdylay_frame_pack_ping) || + !CU_add_test(pSuite, "frame_pack_goaway", + test_spdylay_frame_pack_goaway) || !CU_add_test(pSuite, "frame_pack_headers", test_spdylay_frame_pack_headers) || !CU_add_test(pSuite, "frame_nv_sort", test_spdylay_frame_nv_sort)) { diff --git a/tests/spdylay_frame_test.c b/tests/spdylay_frame_test.c index 4d7da80a..936cc46f 100644 --- a/tests/spdylay_frame_test.c +++ b/tests/spdylay_frame_test.c @@ -71,6 +71,28 @@ void test_spdylay_frame_pack_ping() spdylay_frame_ping_free(&frame.ping); } +void test_spdylay_frame_pack_goaway() +{ + spdylay_frame frame, oframe; + uint8_t *buf; + ssize_t buflen; + spdylay_frame_goaway_init(&frame.goaway, 1000000007); + buflen = spdylay_frame_pack_goaway(&buf, &frame.goaway); + CU_ASSERT(0 == spdylay_frame_unpack_goaway + (&oframe.goaway, + &buf[0], SPDYLAY_FRAME_HEAD_LENGTH, + &buf[SPDYLAY_FRAME_HEAD_LENGTH], + buflen-SPDYLAY_FRAME_HEAD_LENGTH)); + CU_ASSERT(1000000007 == oframe.goaway.last_good_stream_id); + CU_ASSERT(SPDYLAY_PROTO_VERSION == oframe.headers.hd.version); + CU_ASSERT(SPDYLAY_GOAWAY == oframe.headers.hd.type); + CU_ASSERT(SPDYLAY_FLAG_NONE == oframe.headers.hd.flags); + CU_ASSERT(buflen-SPDYLAY_FRAME_HEAD_LENGTH == oframe.ping.hd.length); + free(buf); + spdylay_frame_goaway_free(&oframe.goaway); + spdylay_frame_goaway_free(&frame.goaway); +} + void test_spdylay_frame_pack_headers() { spdylay_zlib deflater, inflater; diff --git a/tests/spdylay_frame_test.h b/tests/spdylay_frame_test.h index c8f3c765..b8f6aed3 100644 --- a/tests/spdylay_frame_test.h +++ b/tests/spdylay_frame_test.h @@ -28,6 +28,7 @@ void test_spdylay_frame_unpack_nv(); void test_spdylay_frame_count_nv_space(); void test_spdylay_frame_pack_ping(); +void test_spdylay_frame_pack_goaway(); void test_spdylay_frame_pack_headers(); void test_spdylay_frame_nv_sort(); diff --git a/tests/spdylay_session_test.c b/tests/spdylay_session_test.c index e78d6b6b..550d1e39 100644 --- a/tests/spdylay_session_test.c +++ b/tests/spdylay_session_test.c @@ -579,3 +579,34 @@ void test_spdylay_session_on_ping_received() spdylay_frame_ping_free(&frame.ping); spdylay_session_del(session); } + +void test_spdylay_session_on_goaway_received() +{ + spdylay_session *session; + spdylay_session_callbacks callbacks = { + NULL, + NULL, + on_ctrl_recv_callback, + on_invalid_ctrl_recv_callback, + }; + my_user_data user_data; + spdylay_frame frame; + spdylay_outbound_item *top; + int32_t stream_id = 1000000007; + user_data.valid = 0; + user_data.invalid = 0; + + spdylay_session_client_new(&session, &callbacks, &user_data); + spdylay_frame_goaway_init(&frame.goaway, stream_id); + + CU_ASSERT(0 == spdylay_session_on_goaway_received(session, &frame)); + CU_ASSERT(1 == user_data.valid); + CU_ASSERT(session->goaway_flags == SPDYLAY_GOAWAY_RECV); + + top = spdylay_session_get_ob_pq_top(session); + CU_ASSERT(SPDYLAY_GOAWAY == top->frame_type); + CU_ASSERT(0 == top->frame->goaway.last_good_stream_id); + + spdylay_frame_goaway_free(&frame.goaway); + spdylay_session_del(session); +} diff --git a/tests/spdylay_session_test.h b/tests/spdylay_session_test.h index 38da5829..31f3ffe7 100644 --- a/tests/spdylay_session_test.h +++ b/tests/spdylay_session_test.h @@ -36,5 +36,6 @@ void test_spdylay_submit_response(); void test_spdylay_session_reply_fail(); void test_spdylay_session_on_headers_received(); void test_spdylay_session_on_ping_received(); +void test_spdylay_session_on_goaway_received(); #endif // SPDYLAY_SESSION_TEST_H