Added GOAWAY handling
This commit is contained in:
parent
cb58e6e893
commit
aed626bfa5
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,8 +560,12 @@ int spdylay_session_send(spdylay_session *session)
|
|||
/* TODO Call error callback? */
|
||||
spdylay_outbound_item_free(item);
|
||||
free(item);
|
||||
if(framebuflen <= SPDYLAY_ERR_FATAL) {
|
||||
return framebuflen;
|
||||
} else {
|
||||
continue;;
|
||||
}
|
||||
}
|
||||
session->aob.item = item;
|
||||
session->aob.framebuf = framebuf;
|
||||
session->aob.framebuflen = framebuflen;
|
||||
|
@ -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,
|
||||
|
|
|
@ -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|.
|
||||
*/
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue