Support transmission of CONTINUATION, change nghttp2_frame_hd

The maximum frame size including header block is still limited
to NGHTTP2_HD_MAX_BUFFER_LENGTH, which is 32KB.
This commit is contained in:
Tatsuhiro Tsujikawa 2014-01-26 22:01:27 +09:00
parent 91401cfe26
commit 9314e30987
12 changed files with 157 additions and 66 deletions

View File

@ -504,7 +504,11 @@ typedef struct {
/**
* The length field of this frame, excluding frame header.
*/
uint16_t length;
size_t length;
/**
* The stream identifier (aka, stream ID)
*/
int32_t stream_id;
/**
* The type of this frame. See `nghttp2_frame`.
*/
@ -513,10 +517,6 @@ typedef struct {
* The flags.
*/
uint8_t flags;
/**
* The stream identifier (aka, stream ID)
*/
int32_t stream_id;
} nghttp2_frame_hd;

View File

@ -230,7 +230,12 @@ ssize_t nghttp2_frame_pack_headers(uint8_t **buf_ptr,
return rv;
}
framelen = rv + nv_offset;
frame->hd.length = framelen - NGHTTP2_FRAME_HEAD_LENGTH;
if(NGHTTP2_FRAME_HEAD_LENGTH + NGHTTP2_MAX_FRAME_LENGTH < rv + nv_offset) {
frame->hd.length = NGHTTP2_MAX_FRAME_LENGTH;
frame->hd.flags &= ~NGHTTP2_FLAG_END_HEADERS;
} else {
frame->hd.length = framelen - NGHTTP2_FRAME_HEAD_LENGTH;
}
/* If frame->nvlen == 0, *buflen_ptr may be smaller than
nv_offset */
rv = nghttp2_reserve_buffer(buf_ptr, buflen_ptr, nv_offset);
@ -387,7 +392,12 @@ ssize_t nghttp2_frame_pack_push_promise(uint8_t **buf_ptr,
return rv;
}
framelen = rv + nv_offset;
frame->hd.length = framelen - NGHTTP2_FRAME_HEAD_LENGTH;
if(NGHTTP2_FRAME_HEAD_LENGTH + NGHTTP2_MAX_FRAME_LENGTH < rv + nv_offset) {
frame->hd.length = NGHTTP2_MAX_FRAME_LENGTH;
frame->hd.flags &= ~NGHTTP2_FLAG_END_HEADERS;
} else {
frame->hd.length = framelen - NGHTTP2_FRAME_HEAD_LENGTH;
}
/* If frame->nvlen == 0, *buflen_ptr may be smaller than
nv_offset */
rv = nghttp2_reserve_buffer(buf_ptr, buflen_ptr, nv_offset);
@ -562,8 +572,8 @@ ssize_t nghttp2_nv_array_copy(nghttp2_nv **nva_ptr,
size_t buflen = 0;
nghttp2_nv *p;
for(i = 0; i < nvlen; ++i) {
if(nva[i].namelen > NGHTTP2_MAX_HD_VALUE_LENGTH ||
nva[i].valuelen > NGHTTP2_MAX_HD_VALUE_LENGTH) {
if(nva[i].namelen > NGHTTP2_HD_MAX_NAME ||
nva[i].valuelen > NGHTTP2_HD_MAX_VALUE) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
buflen += nva[i].namelen + nva[i].valuelen;

View File

@ -33,10 +33,6 @@
#include "nghttp2_hd.h"
#include "nghttp2_buffer.h"
/* The maximum value length of name/value pair. This is not specified
by the spec. We just chose the arbitrary size */
#define NGHTTP2_MAX_HD_VALUE_LENGTH ((1 << 13) - 1)
#define NGHTTP2_FRAME_LENGTH_MASK ((1 << 14) - 1)
#define NGHTTP2_STREAM_ID_MASK ((1u << 31) - 1)
#define NGHTTP2_PRIORITY_MASK ((1u << 31) - 1)
@ -99,7 +95,10 @@ size_t nghttp2_frame_headers_payload_nv_offset(nghttp2_headers *frame);
* change. |*buf_ptr| and |*buflen_ptr| are updated accordingly.
*
* frame->hd.length is assigned after length is determined during
* packing process.
* packing process. If payload length is strictly larger than
* NGHTTP2_MAX_FRAME_LENGTH, payload data is still serialized as is,
* but frame->hd.length is set to NGHTTP2_MAX_FRAME_LENGTH and
* NGHTTP2_FLAG_END_HEADERS flag is cleared from frame->hd.flags.
*
* This function returns the size of packed frame if it succeeds, or
* returns one of the following negative error codes:
@ -242,7 +241,10 @@ int nghttp2_frame_unpack_settings_payload2(nghttp2_settings_entry **iv_ptr,
* change. |*buf_ptr| and |*buflen_ptr| are updated accordingly.
*
* frame->hd.length is assigned after length is determined during
* packing process.
* packing process. If payload length is strictly larger than
* NGHTTP2_MAX_FRAME_LENGTH, payload data is still serialized as is,
* but frame->hd.length is set to NGHTTP2_MAX_FRAME_LENGTH and
* NGHTTP2_FLAG_END_HEADERS flag is cleared from frame->hd.flags.
*
* This function returns the size of packed frame if it succeeds, or
* returns one of the following negative error codes:

View File

@ -443,9 +443,7 @@ static int ensure_write_buffer(uint8_t **buf_ptr, size_t *buflen_ptr,
size_t offset, size_t need)
{
int rv;
/* TODO Remove this limitation when header continuation is
implemented. */
if(need + offset > NGHTTP2_MAX_FRAME_LENGTH) {
if(need + offset > NGHTTP2_HD_MAX_BUFFER_LENGTH) {
return NGHTTP2_ERR_HEADER_COMP;
}
rv = nghttp2_reserve_buffer(buf_ptr, buflen_ptr, offset + need);

View File

@ -37,8 +37,11 @@
#define NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE (1 << 12)
#define NGHTTP2_HD_ENTRY_OVERHEAD 32
/* The maximum value length of name/value pair. This is not specified
by the spec. We just chose the arbitrary size */
#define NGHTTP2_HD_MAX_NAME 256
#define NGHTTP2_HD_MAX_VALUE 4096
#define NGHTTP2_HD_MAX_BUFFER_LENGTH (1 << 15)
/* Default size of maximum table buffer size for encoder. Even if
remote decoder notifies larger buffer size for its decoding,

View File

@ -384,7 +384,7 @@ static void nghttp2_active_outbound_item_reset
nghttp2_outbound_item_free(aob->item);
free(aob->item);
aob->item = NULL;
aob->framebuflen = aob->framebufoff = 0;
aob->framebuflen = aob->framebufoff = aob->framebufmark = 0;
}
void nghttp2_session_del(nghttp2_session *session)
@ -1405,6 +1405,32 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session)
int r;
nghttp2_frame *frame;
frame = nghttp2_outbound_item_get_ctrl_frame(session->aob.item);
if(frame->hd.type == NGHTTP2_HEADERS ||
frame->hd.type == NGHTTP2_PUSH_PROMISE) {
if(session->aob.framebufmark < session->aob.framebuflen) {
nghttp2_frame_hd cont_hd;
cont_hd.length = nghttp2_min(session->aob.framebuflen -
session->aob.framebufmark,
NGHTTP2_MAX_FRAME_LENGTH);
cont_hd.type = NGHTTP2_CONTINUATION;
if(cont_hd.length < NGHTTP2_MAX_FRAME_LENGTH) {
cont_hd.flags = NGHTTP2_FLAG_END_HEADERS;
} else {
cont_hd.flags = NGHTTP2_FLAG_NONE;
}
cont_hd.stream_id = frame->hd.stream_id;
nghttp2_frame_pack_frame_hd(session->aob.framebuf +
session->aob.framebufmark -
NGHTTP2_FRAME_HEAD_LENGTH,
&cont_hd);
session->aob.framebufmark += cont_hd.length;
session->aob.framebufoff -= NGHTTP2_FRAME_HEAD_LENGTH;
return 0;
}
/* Update frame payload length to include data sent in
CONTINUATION frame. */
frame->hd.length = session->aob.framebuflen - NGHTTP2_FRAME_HEAD_LENGTH;
}
if(session->callbacks.on_frame_send_callback) {
if(session->callbacks.on_frame_send_callback(session, frame,
session->user_data) != 0) {
@ -1623,7 +1649,7 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session)
nghttp2_active_outbound_item_reset(&session->aob);
return r;
} else {
session->aob.framebuflen = r;
session->aob.framebuflen = session->aob.framebufmark = r;
session->aob.framebufoff = 0;
}
} else {
@ -1666,7 +1692,8 @@ int nghttp2_session_send(nghttp2_session *session)
framebuflen = nghttp2_session_prep_frame(session, item);
if(framebuflen == NGHTTP2_ERR_DEFERRED) {
continue;
} else if(framebuflen < 0) {
}
if(framebuflen < 0) {
/* TODO If the error comes from compressor, the connection
must be closed. */
if(item->frame_cat == NGHTTP2_CAT_CTRL &&
@ -1700,19 +1727,31 @@ int nghttp2_session_send(nghttp2_session *session)
}
session->aob.item = item;
session->aob.framebuflen = framebuflen;
/* Call before_send callback */
if(item->frame_cat == NGHTTP2_CAT_CTRL &&
session->callbacks.before_frame_send_callback) {
if(session->callbacks.before_frame_send_callback
(session,
nghttp2_outbound_item_get_ctrl_frame(item),
session->user_data) != 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
if(item->frame_cat == NGHTTP2_CAT_CTRL) {
nghttp2_frame *frame = nghttp2_outbound_item_get_ctrl_frame(item);
session->aob.framebufmark =
frame->hd.length + NGHTTP2_FRAME_HEAD_LENGTH;
/* Call before_send callback */
if(session->callbacks.before_frame_send_callback) {
size_t origlen = frame->hd.length;
frame->hd.length =
session->aob.framebuflen - NGHTTP2_FRAME_HEAD_LENGTH;
r = session->callbacks.before_frame_send_callback
(session, frame, session->user_data);
frame->hd.length = origlen;
if(r != 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
}
} else {
session->aob.framebufmark =
nghttp2_outbound_item_get_data_frame
(session->aob.item)->hd.length + NGHTTP2_FRAME_HEAD_LENGTH;
}
}
data = session->aob.framebuf + session->aob.framebufoff;
datalen = session->aob.framebuflen - session->aob.framebufoff;
datalen = session->aob.framebufmark - session->aob.framebufoff;
sentlen = session->callbacks.send_callback(session, data, datalen, 0,
session->user_data);
if(sentlen < 0) {
@ -1742,7 +1781,7 @@ int nghttp2_session_send(nghttp2_session *session)
}
}
session->aob.framebufoff += sentlen;
if(session->aob.framebufoff == session->aob.framebuflen) {
if(session->aob.framebufoff == session->aob.framebufmark) {
/* Frame has completely sent */
r = nghttp2_session_after_frame_sent(session);
if(r < 0) {
@ -1823,6 +1862,8 @@ static int session_call_on_end_headers
(nghttp2_session *session, nghttp2_frame *frame, nghttp2_error_code error_code)
{
int rv;
DEBUGF(fprintf(stderr, "Call on_end_headers callback stream_id=%d\n",
frame->hd.stream_id));
if(session->callbacks.on_end_headers_callback) {
rv = session->callbacks.on_end_headers_callback(session, frame, error_code,
session->user_data);
@ -2167,11 +2208,6 @@ int nghttp2_session_on_request_headers_received(nghttp2_session *session,
return nghttp2_session_inflate_handle_invalid_connection
(session, frame, NGHTTP2_PROTOCOL_ERROR);
}
/* Connection error if header continuation is employed for now */
if((frame->hd.flags & NGHTTP2_FLAG_END_HEADERS) == 0) {
return nghttp2_session_inflate_handle_invalid_connection
(session, frame, NGHTTP2_INTERNAL_ERROR);
}
if(session->goaway_flags) {
/* We don't accept new stream after GOAWAY is sent or received. */
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
@ -2222,11 +2258,6 @@ int nghttp2_session_on_response_headers_received(nghttp2_session *session,
return nghttp2_session_inflate_handle_invalid_connection
(session, frame, NGHTTP2_PROTOCOL_ERROR);
}
/* Connection error if header continuation is employed for now */
if((frame->hd.flags & NGHTTP2_FLAG_END_HEADERS) == 0) {
return nghttp2_session_inflate_handle_invalid_connection
(session, frame, NGHTTP2_INTERNAL_ERROR);
}
if(stream->shut_flags & NGHTTP2_SHUT_RD) {
/* half closed (remote): from the spec:
@ -2255,11 +2286,6 @@ int nghttp2_session_on_push_response_headers_received(nghttp2_session *session,
return nghttp2_session_inflate_handle_invalid_connection
(session, frame, NGHTTP2_PROTOCOL_ERROR);
}
/* Connection error if header continuation is employed for now */
if((frame->hd.flags & NGHTTP2_FLAG_END_HEADERS) == 0) {
return nghttp2_session_inflate_handle_invalid_connection
(session, frame, NGHTTP2_INTERNAL_ERROR);
}
if(session->goaway_flags) {
/* We don't accept new stream after GOAWAY is sent or received. */
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
@ -2286,11 +2312,6 @@ int nghttp2_session_on_headers_received(nghttp2_session *session,
return nghttp2_session_inflate_handle_invalid_connection
(session, frame, NGHTTP2_PROTOCOL_ERROR);
}
/* Connection error if header continuation is employed for now */
if((frame->hd.flags & NGHTTP2_FLAG_END_HEADERS) == 0) {
return nghttp2_session_inflate_handle_invalid_connection
(session, frame, NGHTTP2_INTERNAL_ERROR);
}
if(stream->state == NGHTTP2_STREAM_RESERVED) {
/* reserved. The valid push response HEADERS is processed by
nghttp2_session_on_push_response_headers_received(). This
@ -2848,11 +2869,6 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session,
return nghttp2_session_inflate_handle_invalid_connection
(session, frame, NGHTTP2_PROTOCOL_ERROR);
}
/* Connection error if header continuation is employed for now */
if((frame->hd.flags & NGHTTP2_FLAG_END_PUSH_PROMISE) == 0) {
return nghttp2_session_inflate_handle_invalid_connection
(session, frame, NGHTTP2_INTERNAL_ERROR);
}
if(session->local_settings[NGHTTP2_SETTINGS_ENABLE_PUSH] == 0) {
return nghttp2_session_inflate_handle_invalid_connection
(session, frame, NGHTTP2_PROTOCOL_ERROR);
@ -3601,6 +3617,10 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
readlen = inbound_frame_payload_readlen(iframe, in, last);
DEBUGF(fprintf(stderr, "readlen=%zu, payloadleft=%zu\n",
readlen, iframe->payloadleft));
DEBUGF(fprintf(stderr, "block final=%d\n",
(iframe->frame.hd.flags &
NGHTTP2_FLAG_END_HEADERS) &&
iframe->payloadleft == readlen));
rv = inflate_header_block(session, &iframe->frame, &readlen,
(uint8_t*)in, readlen,
(iframe->frame.hd.flags &
@ -3730,6 +3750,10 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
iframe->payloadleft = cont_hd.length;
if(cont_hd.type != NGHTTP2_CONTINUATION ||
cont_hd.stream_id != iframe->frame.hd.stream_id) {
DEBUGF(fprintf(stderr, "expected stream_id=%d, type=%d, but "
"got stream_id=%d, type=%d\n",
iframe->frame.hd.stream_id, NGHTTP2_CONTINUATION,
cont_hd.stream_id, cont_hd.type));
rv = nghttp2_session_terminate_session(session,
NGHTTP2_PROTOCOL_ERROR);
if(nghttp2_is_fatal(rv)) {
@ -4024,6 +4048,7 @@ ssize_t nghttp2_session_pack_data(nghttp2_session *session,
/* This is the error code when callback is failed. */
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
frame->hd.length = r;
memset(*buf_ptr, 0, NGHTTP2_FRAME_HEAD_LENGTH);
nghttp2_put_uint16be(&(*buf_ptr)[0], r);
flags = 0;

View File

@ -60,6 +60,9 @@ typedef struct {
size_t framebuflen;
/* The number of bytes has been sent */
size_t framebufoff;
/* Marks the last position to send. This is used to implement
CONTINUATION */
size_t framebufmark;
} nghttp2_active_outbound_item;
/* Buffer length for inbound raw byte stream. */

View File

@ -438,7 +438,7 @@ void print_data_frame(print_type ptype, uint16_t length, uint8_t flags,
{
printf("%sDATA%s frame ",
frame_name_ansi_esc(ptype), ansi_escend());
nghttp2_frame_hd hd = {length, NGHTTP2_DATA, flags, stream_id};
nghttp2_frame_hd hd = {length, stream_id, NGHTTP2_DATA, flags};
print_frame_hd(hd);
if(flags) {
print_frame_attr_indent();

View File

@ -144,6 +144,8 @@ int main(int argc, char* argv[])
!CU_add_test(pSuite, "submit_headers_push_reply",
test_nghttp2_submit_headers_push_reply) ||
!CU_add_test(pSuite, "submit_headers", test_nghttp2_submit_headers) ||
!CU_add_test(pSuite, "submit_headers_continuation",
test_nghttp2_submit_headers_continuation) ||
!CU_add_test(pSuite, "submit_priority", test_nghttp2_submit_priority) ||
!CU_add_test(pSuite, "session_submit_settings",
test_nghttp2_submit_settings) ||

View File

@ -143,8 +143,8 @@ void test_nghttp2_frame_pack_headers_frame_too_large(void)
ssize_t framelen;
nghttp2_nv *nva;
ssize_t nvlen;
size_t big_vallen = NGHTTP2_MAX_HD_VALUE_LENGTH;
nghttp2_nv big_hds[8];
size_t big_vallen = NGHTTP2_HD_MAX_VALUE;
nghttp2_nv big_hds[16];
size_t big_hdslen = ARRLEN(big_hds);
size_t i;

View File

@ -734,7 +734,7 @@ void test_nghttp2_session_recv_continuation(void)
ud.header_cb_called = 0;
rv = nghttp2_session_mem_recv(session, data, datalen);
CU_ASSERT(rv == datalen);
CU_ASSERT((ssize_t)datalen == rv);
CU_ASSERT(2 == ud.header_cb_called);
nghttp2_hd_deflate_free(&deflater);
@ -765,7 +765,7 @@ void test_nghttp2_session_recv_continuation(void)
ud.end_headers_cb_called = 0;
rv = nghttp2_session_mem_recv(session, data, datalen);
CU_ASSERT(datalen == rv);
CU_ASSERT((ssize_t)datalen == rv);
CU_ASSERT(1 == ud.end_headers_cb_called);
CU_ASSERT(NGHTTP2_GOAWAY ==
@ -847,7 +847,8 @@ void test_nghttp2_session_continue(void)
recv_frame = user_data.frame;
CU_ASSERT(NGHTTP2_HEADERS == recv_frame->hd.type);
CU_ASSERT(framelen1 - NGHTTP2_FRAME_HEAD_LENGTH == recv_frame->hd.length);
CU_ASSERT((size_t)framelen1 - NGHTTP2_FRAME_HEAD_LENGTH ==
recv_frame->hd.length);
CU_ASSERT(1 == user_data.header_cb_called);
CU_ASSERT(0 == user_data.end_headers_cb_called);
@ -877,7 +878,8 @@ void test_nghttp2_session_continue(void)
recv_frame = user_data.frame;
CU_ASSERT(NGHTTP2_HEADERS == recv_frame->hd.type);
CU_ASSERT(framelen2 - NGHTTP2_FRAME_HEAD_LENGTH == recv_frame->hd.length);
CU_ASSERT((size_t)framelen2 - NGHTTP2_FRAME_HEAD_LENGTH ==
recv_frame->hd.length);
CU_ASSERT(1 == user_data.header_cb_called);
CU_ASSERT(1 == user_data.end_headers_cb_called);
@ -1801,7 +1803,6 @@ void test_nghttp2_session_send_headers_reply(void)
nghttp2_session_del(session);
}
/* TODO Rewrite this test when header continuation is supported */
void test_nghttp2_session_send_headers_header_comp_error(void)
{
nghttp2_session *session;
@ -1809,8 +1810,8 @@ void test_nghttp2_session_send_headers_header_comp_error(void)
nghttp2_frame *frame = malloc(sizeof(nghttp2_frame));
nghttp2_nv *nva;
ssize_t nvlen;
size_t vallen = NGHTTP2_MAX_HD_VALUE_LENGTH;
nghttp2_nv nv[8];
size_t vallen = NGHTTP2_HD_MAX_VALUE;
nghttp2_nv nv[16];
size_t nnv = ARRLEN(nv);
size_t i;
@ -2455,6 +2456,52 @@ void test_nghttp2_submit_headers(void)
nghttp2_session_del(session);
}
void test_nghttp2_submit_headers_continuation(void)
{
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_nv nv[] = {
MAKE_NV("h1", ""),
MAKE_NV("h1", ""),
MAKE_NV("h1", ""),
MAKE_NV("h1", ""),
MAKE_NV("h1", ""),
MAKE_NV("h1", ""),
MAKE_NV("h1", ""),
};
nghttp2_outbound_item *item;
uint8_t data[4096];
size_t i;
my_user_data ud;
memset(data, '0', sizeof(data));
for(i = 0; i < ARRLEN(nv); ++i) {
nv[i].valuelen = sizeof(data);
nv[i].value = data;
}
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback;
callbacks.on_frame_send_callback = on_frame_send_callback;
CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud));
CU_ASSERT(0 == nghttp2_submit_headers(session,
NGHTTP2_FLAG_END_STREAM,
-1, NGHTTP2_PRI_DEFAULT,
nv, ARRLEN(nv), NULL));
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_HEADERS == OB_CTRL_TYPE(item));
CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) ==
OB_CTRL(item)->hd.flags);
CU_ASSERT(NGHTTP2_PRI_DEFAULT == OB_CTRL(item)->headers.pri);
ud.frame_send_cb_called = 0;
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(1 == ud.frame_send_cb_called);
nghttp2_session_del(session);
}
void test_nghttp2_submit_priority(void)
{
nghttp2_session *session;

View File

@ -62,6 +62,7 @@ void test_nghttp2_submit_headers_start_stream(void);
void test_nghttp2_submit_headers_reply(void);
void test_nghttp2_submit_headers_push_reply(void);
void test_nghttp2_submit_headers(void);
void test_nghttp2_submit_headers_continuation(void);
void test_nghttp2_submit_priority(void);
void test_nghttp2_submit_settings(void);
void test_nghttp2_submit_settings_update_local_window_size(void);