Added GOAWAY handling

This commit is contained in:
Tatsuhiro Tsujikawa 2012-01-28 19:22:38 +09:00
parent cb58e6e893
commit aed626bfa5
10 changed files with 247 additions and 3 deletions

View File

@ -125,6 +125,12 @@ typedef struct {
uint32_t unique_id; uint32_t unique_id;
} spdylay_ping; } spdylay_ping;
typedef struct {
spdylay_ctrl_hd hd;
int32_t last_good_stream_id;
uint32_t status_code;
} spdylay_goaway;
typedef union { typedef union {
int fd; int fd;
void *ptr; void *ptr;
@ -150,6 +156,7 @@ typedef union {
spdylay_syn_reply syn_reply; spdylay_syn_reply syn_reply;
spdylay_rst_stream rst_stream; spdylay_rst_stream rst_stream;
spdylay_ping ping; spdylay_ping ping;
spdylay_goaway goaway;
spdylay_headers headers; spdylay_headers headers;
spdylay_data data; spdylay_data data;
} spdylay_frame; } spdylay_frame;

View File

@ -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_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, void spdylay_frame_headers_init(spdylay_headers *frame, uint8_t flags,
int32_t stream_id, char **nv) int32_t stream_id, char **nv)
{ {
@ -551,6 +564,34 @@ int spdylay_frame_unpack_ping(spdylay_ping *frame,
return 0; 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 #define SPDYLAY_HEADERS_NV_OFFSET 14
ssize_t spdylay_frame_pack_headers(uint8_t **buf_ptr, ssize_t spdylay_frame_pack_headers(uint8_t **buf_ptr,

View File

@ -101,6 +101,22 @@ int spdylay_frame_unpack_ping(spdylay_ping *frame,
const uint8_t *head, size_t headlen, const uint8_t *head, size_t headlen,
const uint8_t *payload, size_t payloadlen); 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 * Packs HEADERS frame |frame| in wire format and store it in
* |*buf_ptr|. This function allocates enough memory in |*buf_ptr| to * |*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_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, void spdylay_frame_headers_init(spdylay_headers *frame, uint8_t flags,
int32_t stream_id, char **nv); int32_t stream_id, char **nv);

View File

@ -80,6 +80,9 @@ int spdylay_session_client_new(spdylay_session **session_ptr,
(*session_ptr)->last_ping_unique_id = 0; (*session_ptr)->last_ping_unique_id = 0;
memset(&(*session_ptr)->last_ping_time, 0, sizeof(struct timespec)); 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); r = spdylay_zlib_deflate_hd_init(&(*session_ptr)->hd_deflater);
if(r != 0) { if(r != 0) {
free(*session_ptr); free(*session_ptr);
@ -144,6 +147,9 @@ static void spdylay_outbound_item_free(spdylay_outbound_item *item)
case SPDYLAY_PING: case SPDYLAY_PING:
spdylay_frame_ping_free(&item->frame->ping); spdylay_frame_ping_free(&item->frame->ping);
break; break;
case SPDYLAY_GOAWAY:
spdylay_frame_goaway_free(&item->frame->goaway);
break;
case SPDYLAY_HEADERS: case SPDYLAY_HEADERS:
spdylay_frame_headers_free(&item->frame->headers); spdylay_frame_headers_free(&item->frame->headers);
break; break;
@ -215,6 +221,9 @@ int spdylay_session_add_frame(spdylay_session *session,
/* Ping has "height" priority. Give it -1. */ /* Ping has "height" priority. Give it -1. */
item->pri = -1; item->pri = -1;
break; break;
case SPDYLAY_GOAWAY:
/* Should GOAWAY have higher priority? */
break;
case SPDYLAY_HEADERS: case SPDYLAY_HEADERS:
/* Currently we don't have any API to send HEADERS frame, so this /* Currently we don't have any API to send HEADERS frame, so this
is unreachable. */ is unreachable. */
@ -328,6 +337,11 @@ ssize_t spdylay_session_prep_frame(spdylay_session *session,
int r; int r;
switch(item->frame_type) { switch(item->frame_type) {
case SPDYLAY_SYN_STREAM: { 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; item->frame->syn_stream.stream_id = session->next_stream_id;
session->next_stream_id += 2; session->next_stream_id += 2;
framebuflen = spdylay_frame_pack_syn_stream(&framebuf, 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 /* Currently we don't have any API to send HEADERS frame, so this
is unreachable. */ is unreachable. */
abort(); 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: { case SPDYLAY_DATA: {
if(!spdylay_session_is_data_allowed(session, item->frame->data.stream_id)) { if(!spdylay_session_is_data_allowed(session, item->frame->data.stream_id)) {
return SPDYLAY_ERR_INVALID_FRAME; 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? */ /* TODO If clock_gettime() fails, what should we do? */
clock_gettime(CLOCK_MONOTONIC, &session->last_ping_time); clock_gettime(CLOCK_MONOTONIC, &session->last_ping_time);
break; break;
case SPDYLAY_GOAWAY:
session->goaway_flags |= SPDYLAY_GOAWAY_SEND;
break;
case SPDYLAY_HEADERS: case SPDYLAY_HEADERS:
/* Currently we don't have any API to send HEADERS frame, so this /* Currently we don't have any API to send HEADERS frame, so this
is unreachable. */ is unreachable. */
@ -530,7 +560,11 @@ int spdylay_session_send(spdylay_session *session)
/* TODO Call error callback? */ /* TODO Call error callback? */
spdylay_outbound_item_free(item); spdylay_outbound_item_free(item);
free(item); free(item);
continue;; if(framebuflen <= SPDYLAY_ERR_FATAL) {
return framebuflen;
} else {
continue;;
}
} }
session->aob.item = item; session->aob.item = item;
session->aob.framebuf = framebuf; session->aob.framebuf = framebuf;
@ -686,6 +720,10 @@ int spdylay_session_on_syn_stream_received(spdylay_session *session,
spdylay_frame *frame) spdylay_frame *frame)
{ {
int r; 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) { if(spdylay_session_validate_syn_stream(session, &frame->syn_stream) == 0) {
uint8_t flags = frame->syn_stream.hd.flags; uint8_t flags = frame->syn_stream.hd.flags;
if((flags & SPDYLAY_FLAG_FIN) && (flags & SPDYLAY_FLAG_UNIDIRECTIONAL)) { if((flags & SPDYLAY_FLAG_FIN) && (flags & SPDYLAY_FLAG_UNIDIRECTIONAL)) {
@ -786,6 +824,25 @@ int spdylay_session_on_ping_received(spdylay_session *session,
return r; 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, int spdylay_session_on_headers_received(spdylay_session *session,
spdylay_frame *frame) spdylay_frame *frame)
{ {
@ -895,6 +952,17 @@ int spdylay_session_process_ctrl_frame(spdylay_session *session)
spdylay_frame_ping_free(&frame.ping); spdylay_frame_ping_free(&frame.ping);
} }
break; 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: case SPDYLAY_HEADERS:
r = spdylay_frame_unpack_headers(&frame.headers, r = spdylay_frame_unpack_headers(&frame.headers,
session->iframe.headbuf, session->iframe.headbuf,
@ -1025,12 +1093,19 @@ int spdylay_session_recv(spdylay_session *session)
int spdylay_session_want_read(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) 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) 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; 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) int spdylay_submit_ping(spdylay_session *session)
{ {
return spdylay_session_add_ping(session, return spdylay_session_add_ping(session,

View File

@ -79,6 +79,14 @@ typedef struct {
uint8_t ign; uint8_t ign;
} spdylay_inbound_frame; } 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 { typedef struct spdylay_session {
uint8_t server; uint8_t server;
int32_t next_stream_id; int32_t next_stream_id;
@ -103,6 +111,12 @@ typedef struct spdylay_session {
/* Time stamp when last ping is sent. */ /* Time stamp when last ping is sent. */
struct timespec last_ping_time; 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; spdylay_session_callbacks callbacks;
void *user_data; void *user_data;
} spdylay_session; } 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_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|, * Creates new stream in |session| with stream ID |stream_id|,
* priority |pri| and flags |flags|. Currently, |flags| & * 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, int spdylay_session_on_ping_received(spdylay_session *session,
spdylay_frame *frame); 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|. * Called when HEADERS is recieved. Received frame is |frame|.
*/ */

View File

@ -86,10 +86,14 @@ int main()
test_spdylay_session_on_headers_received) || test_spdylay_session_on_headers_received) ||
!CU_add_test(pSuite, "session_on_ping_received", !CU_add_test(pSuite, "session_on_ping_received",
test_spdylay_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_unpack_nv", test_spdylay_frame_unpack_nv) ||
!CU_add_test(pSuite, "frame_count_nv_space", !CU_add_test(pSuite, "frame_count_nv_space",
test_spdylay_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_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", !CU_add_test(pSuite, "frame_pack_headers",
test_spdylay_frame_pack_headers) || test_spdylay_frame_pack_headers) ||
!CU_add_test(pSuite, "frame_nv_sort", test_spdylay_frame_nv_sort)) { !CU_add_test(pSuite, "frame_nv_sort", test_spdylay_frame_nv_sort)) {

View File

@ -71,6 +71,28 @@ void test_spdylay_frame_pack_ping()
spdylay_frame_ping_free(&frame.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() void test_spdylay_frame_pack_headers()
{ {
spdylay_zlib deflater, inflater; spdylay_zlib deflater, inflater;

View File

@ -28,6 +28,7 @@
void test_spdylay_frame_unpack_nv(); void test_spdylay_frame_unpack_nv();
void test_spdylay_frame_count_nv_space(); void test_spdylay_frame_count_nv_space();
void test_spdylay_frame_pack_ping(); void test_spdylay_frame_pack_ping();
void test_spdylay_frame_pack_goaway();
void test_spdylay_frame_pack_headers(); void test_spdylay_frame_pack_headers();
void test_spdylay_frame_nv_sort(); void test_spdylay_frame_nv_sort();

View File

@ -579,3 +579,34 @@ void test_spdylay_session_on_ping_received()
spdylay_frame_ping_free(&frame.ping); spdylay_frame_ping_free(&frame.ping);
spdylay_session_del(session); 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);
}

View File

@ -36,5 +36,6 @@ void test_spdylay_submit_response();
void test_spdylay_session_reply_fail(); void test_spdylay_session_reply_fail();
void test_spdylay_session_on_headers_received(); void test_spdylay_session_on_headers_received();
void test_spdylay_session_on_ping_received(); void test_spdylay_session_on_ping_received();
void test_spdylay_session_on_goaway_received();
#endif // SPDYLAY_SESSION_TEST_H #endif // SPDYLAY_SESSION_TEST_H