Support CONTINUATION frame reception

This commit is contained in:
Tatsuhiro Tsujikawa 2014-01-26 20:31:28 +09:00
parent e7fc2951b8
commit 91401cfe26
7 changed files with 197 additions and 10 deletions

View File

@ -364,7 +364,11 @@ typedef enum {
/** /**
* The WINDOW_UPDATE frame. * The WINDOW_UPDATE frame.
*/ */
NGHTTP2_WINDOW_UPDATE = 9 NGHTTP2_WINDOW_UPDATE = 9,
/**
* The CONTINUATION frame.
*/
NGHTTP2_CONTINUATION = 10
} nghttp2_frame_type; } nghttp2_frame_type;
/** /**

View File

@ -250,10 +250,6 @@ int nghttp2_frame_unpack_headers_payload(nghttp2_headers *frame,
const uint8_t *payload, const uint8_t *payload,
size_t payloadlen) size_t payloadlen)
{ {
/* TODO Return error if header continuation is used for now */
if((frame->hd.flags & NGHTTP2_FLAG_END_HEADERS) == 0) {
return NGHTTP2_ERR_PROTO;
}
if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY) { if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY) {
assert(payloadlen == 4); assert(payloadlen == 4);
frame->pri = nghttp2_get_uint32(payload) & NGHTTP2_PRIORITY_MASK; frame->pri = nghttp2_get_uint32(payload) & NGHTTP2_PRIORITY_MASK;
@ -409,10 +405,6 @@ int nghttp2_frame_unpack_push_promise_payload(nghttp2_push_promise *frame,
const uint8_t *payload, const uint8_t *payload,
size_t payloadlen) size_t payloadlen)
{ {
/* TODO Return error if header continuation is used for now */
if((frame->hd.flags & NGHTTP2_FLAG_END_PUSH_PROMISE) == 0) {
return NGHTTP2_ERR_PROTO;
}
frame->promised_stream_id = nghttp2_get_uint32(payload) & frame->promised_stream_id = nghttp2_get_uint32(payload) &
NGHTTP2_STREAM_ID_MASK; NGHTTP2_STREAM_ID_MASK;
frame->nva = NULL; frame->nva = NULL;

View File

@ -3398,6 +3398,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
size_t readlen; size_t readlen;
int rv; int rv;
int busy = 0; int busy = 0;
nghttp2_frame_hd cont_hd;
for(;;) { for(;;) {
switch(iframe->state) { switch(iframe->state) {
@ -3408,6 +3409,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
if(iframe->left) { if(iframe->left) {
return in - first; return in - first;
} }
nghttp2_frame_unpack_frame_hd(&iframe->frame.hd, iframe->buf); nghttp2_frame_unpack_frame_hd(&iframe->frame.hd, iframe->buf);
iframe->payloadleft = iframe->frame.hd.length; iframe->payloadleft = iframe->frame.hd.length;
iframe->buflen = 0; iframe->buflen = 0;
@ -3505,6 +3507,15 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
iframe->state = NGHTTP2_IB_READ_NBYTE; iframe->state = NGHTTP2_IB_READ_NBYTE;
iframe->left = 8; iframe->left = 8;
break; break;
case NGHTTP2_CONTINUATION:
rv = nghttp2_session_terminate_session(session,
NGHTTP2_PROTOCOL_ERROR);
if(nghttp2_is_fatal(rv)) {
return rv;
}
busy = 1;
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
break;
default: default:
busy = 1; busy = 1;
iframe->state = NGHTTP2_IB_IGN_PAYLOAD; iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
@ -3592,6 +3603,8 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
readlen, iframe->payloadleft)); readlen, iframe->payloadleft));
rv = inflate_header_block(session, &iframe->frame, &readlen, rv = inflate_header_block(session, &iframe->frame, &readlen,
(uint8_t*)in, readlen, (uint8_t*)in, readlen,
(iframe->frame.hd.flags &
NGHTTP2_FLAG_END_HEADERS) &&
iframe->payloadleft == readlen, iframe->payloadleft == readlen,
iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK); iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK);
if(nghttp2_is_fatal(rv)) { if(nghttp2_is_fatal(rv)) {
@ -3620,7 +3633,18 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
return rv; return rv;
} }
} }
nghttp2_inbound_frame_reset(session); if((iframe->frame.hd.flags & NGHTTP2_FLAG_END_HEADERS) == 0) {
iframe->left = NGHTTP2_FRAME_HEAD_LENGTH;
iframe->error_code = 0;
iframe->buflen = 0;
if(iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK) {
iframe->state = NGHTTP2_IB_EXPECT_CONTINUATION;
} else {
iframe->state = NGHTTP2_IB_IGN_CONTINUATION;
}
} else {
nghttp2_inbound_frame_reset(session);
}
break; break;
case NGHTTP2_IB_IGN_PAYLOAD: case NGHTTP2_IB_IGN_PAYLOAD:
readlen = inbound_frame_payload_readlen(iframe, in, last); readlen = inbound_frame_payload_readlen(iframe, in, last);
@ -3688,6 +3712,52 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
} }
nghttp2_inbound_frame_reset(session); nghttp2_inbound_frame_reset(session);
break; break;
case NGHTTP2_IB_EXPECT_CONTINUATION:
case NGHTTP2_IB_IGN_CONTINUATION:
#ifdef DEBUGBUILD
if(iframe->state == NGHTTP2_IB_EXPECT_CONTINUATION) {
fprintf(stderr, "[IB_EXPECT_CONTINUATION]\n");
} else {
fprintf(stderr, "[IB_IGN_CONTINUATION]\n");
}
#endif /* DEBUGBUILD */
readlen = inbound_frame_buf_read(iframe, in, last);
in += readlen;
if(iframe->left) {
return in - first;
}
nghttp2_frame_unpack_frame_hd(&cont_hd, iframe->buf);
iframe->payloadleft = cont_hd.length;
if(cont_hd.type != NGHTTP2_CONTINUATION ||
cont_hd.stream_id != iframe->frame.hd.stream_id) {
rv = nghttp2_session_terminate_session(session,
NGHTTP2_PROTOCOL_ERROR);
if(nghttp2_is_fatal(rv)) {
return rv;
}
rv = session_call_on_end_headers(session, &iframe->frame,
NGHTTP2_PROTOCOL_ERROR);
if(nghttp2_is_fatal(rv)) {
return rv;
}
/* Mark inflater bad so that we won't perform further decoding */
session->hd_inflater.ctx.bad = 1;
busy = 1;
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
break;
}
if(cont_hd.flags & NGHTTP2_FLAG_END_HEADERS) {
iframe->frame.hd.flags |= NGHTTP2_FLAG_END_HEADERS;
}
busy = 1;
if(iframe->state == NGHTTP2_IB_EXPECT_CONTINUATION) {
iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK;
} else {
iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK;
}
break;
case NGHTTP2_IB_READ_DATA: case NGHTTP2_IB_READ_DATA:
DEBUGF(fprintf(stderr, "[IB_READ_DATA]\n")); DEBUGF(fprintf(stderr, "[IB_READ_DATA]\n"));
readlen = inbound_frame_payload_readlen(iframe, in, last); readlen = inbound_frame_payload_readlen(iframe, in, last);

View File

@ -81,6 +81,8 @@ typedef enum {
NGHTTP2_IB_FRAME_SIZE_ERROR, NGHTTP2_IB_FRAME_SIZE_ERROR,
NGHTTP2_IB_READ_SETTINGS, NGHTTP2_IB_READ_SETTINGS,
NGHTTP2_IB_READ_GOAWAY_DEBUG, NGHTTP2_IB_READ_GOAWAY_DEBUG,
NGHTTP2_IB_EXPECT_CONTINUATION,
NGHTTP2_IB_IGN_CONTINUATION,
NGHTTP2_IB_READ_DATA, NGHTTP2_IB_READ_DATA,
NGHTTP2_IB_IGN_DATA NGHTTP2_IB_IGN_DATA
} nghttp2_inbound_state; } nghttp2_inbound_state;

View File

@ -83,6 +83,8 @@ int main(int argc, char* argv[])
test_nghttp2_session_recv_eof) || test_nghttp2_session_recv_eof) ||
!CU_add_test(pSuite, "session_recv_data", !CU_add_test(pSuite, "session_recv_data",
test_nghttp2_session_recv_data) || test_nghttp2_session_recv_data) ||
!CU_add_test(pSuite, "session_recv_continuation",
test_nghttp2_session_recv_continuation) ||
!CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) || !CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) ||
!CU_add_test(pSuite, "session_add_frame", !CU_add_test(pSuite, "session_add_frame",
test_nghttp2_session_add_frame) || test_nghttp2_session_add_frame) ||

View File

@ -660,6 +660,122 @@ void test_nghttp2_session_recv_data(void)
nghttp2_session_del(session); nghttp2_session_del(session);
} }
void test_nghttp2_session_recv_continuation(void)
{
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
const nghttp2_nv nv1[] = {
MAKE_NV("method", "GET"),
MAKE_NV("path", "/")
};
nghttp2_nv *nva;
size_t nvlen;
nghttp2_frame frame;
uint8_t *framedata = NULL;
size_t framedatacap = 0;
size_t framedatalen;
size_t framedataoff;
ssize_t rv;
my_user_data ud;
nghttp2_hd_deflater deflater;
uint8_t data[1024];
size_t datalen;
nghttp2_frame_hd cont_hd;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.on_header_callback = on_header_callback;
callbacks.on_end_headers_callback = on_end_headers_callback;
nghttp2_session_server_new(&session, &callbacks, &ud);
nghttp2_hd_deflate_init(&deflater, NGHTTP2_HD_SIDE_REQUEST);
/* Make 1 HEADERS and insert CONTINUATION header */
nvlen = nghttp2_nv_array_copy(&nva, nv1, ARRLEN(nv1));
nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE,
1, NGHTTP2_PRI_DEFAULT, nva, nvlen);
framedatalen = nghttp2_frame_pack_headers(&framedata, &framedatacap,
&frame.headers,
&deflater);
nghttp2_frame_headers_free(&frame.headers);
memcpy(data, framedata, 9);
datalen = 9;
framedataoff = NGHTTP2_FRAME_HEAD_LENGTH + 1;
nghttp2_put_uint16be(data, 1);
/* First CONTINUATION, 2 bytes */
cont_hd.length = 2;
cont_hd.type = NGHTTP2_CONTINUATION;
cont_hd.flags = NGHTTP2_FLAG_NONE;
cont_hd.stream_id = 1;
nghttp2_frame_pack_frame_hd(data + datalen, &cont_hd);
datalen += NGHTTP2_FRAME_HEAD_LENGTH;
memcpy(data + datalen, framedata + framedataoff, cont_hd.length);
datalen += cont_hd.length;
framedataoff += cont_hd.length;
/* Second CONTINUATION, rest of the bytes */
cont_hd.length = framedatalen - framedataoff;
cont_hd.flags = NGHTTP2_FLAG_END_HEADERS;
cont_hd.stream_id = 1;
nghttp2_frame_pack_frame_hd(data + datalen, &cont_hd);
datalen += NGHTTP2_FRAME_HEAD_LENGTH;
memcpy(data + datalen, framedata + framedataoff, cont_hd.length);
datalen += cont_hd.length;
framedataoff += cont_hd.length;
assert(framedataoff == framedatalen);
ud.header_cb_called = 0;
rv = nghttp2_session_mem_recv(session, data, datalen);
CU_ASSERT(rv == datalen);
CU_ASSERT(2 == ud.header_cb_called);
nghttp2_hd_deflate_free(&deflater);
nghttp2_session_del(session);
/* Expecting CONTINUATION, but get the other frame */
nghttp2_session_server_new(&session, &callbacks, &ud);
nghttp2_hd_deflate_init(&deflater, NGHTTP2_HD_SIDE_REQUEST);
/* HEADERS without END_HEADERS flag */
nvlen = nghttp2_nv_array_copy(&nva, nv1, ARRLEN(nv1));
nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE,
1, NGHTTP2_PRI_DEFAULT, nva, nvlen);
framedatalen = nghttp2_frame_pack_headers(&framedata, &framedatacap,
&frame.headers,
&deflater);
nghttp2_frame_headers_free(&frame.headers);
memcpy(data, framedata, framedatalen);
datalen = framedatalen;
/* Followed by PRIORITY */
nghttp2_frame_priority_init(&frame.priority, 1, 0);
framedatalen = nghttp2_frame_pack_priority(&framedata, &framedatacap,
&frame.priority);
memcpy(data + datalen, framedata, framedatalen);
datalen += framedatalen;
ud.end_headers_cb_called = 0;
rv = nghttp2_session_mem_recv(session, data, datalen);
CU_ASSERT(datalen == rv);
CU_ASSERT(1 == ud.end_headers_cb_called);
CU_ASSERT(NGHTTP2_GOAWAY ==
OB_CTRL_TYPE(nghttp2_session_get_next_ob_item(session)));
free(framedata);
nghttp2_hd_deflate_free(&deflater);
nghttp2_session_del(session);
}
void test_nghttp2_session_continue(void) void test_nghttp2_session_continue(void)
{ {
nghttp2_session *session; nghttp2_session *session;

View File

@ -30,6 +30,7 @@ void test_nghttp2_session_recv_invalid_stream_id(void);
void test_nghttp2_session_recv_invalid_frame(void); void test_nghttp2_session_recv_invalid_frame(void);
void test_nghttp2_session_recv_eof(void); void test_nghttp2_session_recv_eof(void);
void test_nghttp2_session_recv_data(void); void test_nghttp2_session_recv_data(void);
void test_nghttp2_session_recv_continuation(void);
void test_nghttp2_session_continue(void); void test_nghttp2_session_continue(void);
void test_nghttp2_session_add_frame(void); void test_nghttp2_session_add_frame(void);
void test_nghttp2_session_on_request_headers_received(void); void test_nghttp2_session_on_request_headers_received(void);