Support CONTINUATION frame reception
This commit is contained in:
parent
e7fc2951b8
commit
91401cfe26
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) ||
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue