Handle overflow in initial window update in stream

Rename window_size in nghttp2_stream as remote_window_size.
This commit is contained in:
Tatsuhiro Tsujikawa 2013-08-08 21:12:49 +09:00
parent dcfa421d6f
commit e67096fef3
5 changed files with 81 additions and 39 deletions

View File

@ -71,6 +71,13 @@ typedef struct nghttp2_session nghttp2_session;
*/ */
#define NGHTTP2_PRI_LOWEST ((1U << 31) - 1) #define NGHTTP2_PRI_LOWEST ((1U << 31) - 1)
/**
* @macro
*
* The maximum window size
*/
#define NGHTTP2_MAX_WINDOW_SIZE ((int32_t)((1U << 31) - 1))
/** /**
* @macro * @macro
* *
@ -207,6 +214,10 @@ typedef enum {
* Header block inflate/deflate error. * Header block inflate/deflate error.
*/ */
NGHTTP2_ERR_HEADER_COMP = -523, NGHTTP2_ERR_HEADER_COMP = -523,
/**
* Flow control error
*/
NGHTTP2_ERR_FLOW_CONTROL = -524,
/** /**
* The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is * The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is
* under unexpected condition and cannot process any further data * under unexpected condition and cannot process any further data

View File

@ -457,6 +457,8 @@ nghttp2_stream* nghttp2_session_open_stream(nghttp2_session *session,
[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS], [NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS],
session->remote_settings session->remote_settings
[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE], [NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE],
session->local_settings
[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE],
stream_user_data); stream_user_data);
r = nghttp2_map_insert(&session->streams, &stream->map_entry); r = nghttp2_map_insert(&session->streams, &stream->map_entry);
if(r != 0) { if(r != 0) {
@ -818,7 +820,7 @@ static size_t nghttp2_session_next_data_read(nghttp2_session *session,
int32_t session_window_size = int32_t session_window_size =
session->remote_flow_control ? session->window_size : INT32_MAX; session->remote_flow_control ? session->window_size : INT32_MAX;
int32_t stream_window_size = int32_t stream_window_size =
stream->remote_flow_control ? stream->window_size : INT32_MAX; stream->remote_flow_control ? stream->remote_window_size : INT32_MAX;
int32_t window_size = nghttp2_min(session_window_size, int32_t window_size = nghttp2_min(session_window_size,
stream_window_size); stream_window_size);
if(window_size > 0) { if(window_size > 0) {
@ -1509,7 +1511,7 @@ int nghttp2_session_send(nghttp2_session *session)
frame = nghttp2_outbound_item_get_data_frame(session->aob.item); frame = nghttp2_outbound_item_get_data_frame(session->aob.item);
stream = nghttp2_session_get_stream(session, frame->hd.stream_id); stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if(stream && stream->remote_flow_control) { if(stream && stream->remote_flow_control) {
stream->window_size -= len; stream->remote_window_size -= len;
} }
if(session->remote_flow_control) { if(session->remote_flow_control) {
session->window_size -= len; session->window_size -= len;
@ -1857,21 +1859,24 @@ int nghttp2_session_on_rst_stream_received(nghttp2_session *session,
static int nghttp2_update_initial_window_size_func(nghttp2_map_entry *entry, static int nghttp2_update_initial_window_size_func(nghttp2_map_entry *entry,
void *ptr) void *ptr)
{ {
int rv;
nghttp2_update_window_size_arg *arg; nghttp2_update_window_size_arg *arg;
nghttp2_stream *stream; nghttp2_stream *stream;
arg = (nghttp2_update_window_size_arg*)ptr; arg = (nghttp2_update_window_size_arg*)ptr;
stream = (nghttp2_stream*)entry; stream = (nghttp2_stream*)entry;
nghttp2_stream_update_initial_window_size(stream, rv = nghttp2_stream_update_remote_initial_window_size(stream,
arg->new_window_size, arg->new_window_size,
arg->old_window_size); arg->old_window_size);
if(rv != 0) {
return NGHTTP2_ERR_FLOW_CONTROL;
}
/* If window size gets positive, push deferred DATA frame to /* If window size gets positive, push deferred DATA frame to
outbound queue. */ outbound queue. */
if(stream->deferred_data && if(stream->deferred_data &&
(stream->deferred_flags & NGHTTP2_DEFERRED_FLOW_CONTROL) && (stream->deferred_flags & NGHTTP2_DEFERRED_FLOW_CONTROL) &&
stream->window_size > 0 && stream->remote_window_size > 0 &&
(arg->session->remote_flow_control == 0 || (arg->session->remote_flow_control == 0 ||
arg->session->window_size > 0)) { arg->session->window_size > 0)) {
int rv;
rv = nghttp2_pq_push(&arg->session->ob_pq, stream->deferred_data); rv = nghttp2_pq_push(&arg->session->ob_pq, stream->deferred_data);
if(rv == 0) { if(rv == 0) {
nghttp2_stream_detach_deferred_data(stream); nghttp2_stream_detach_deferred_data(stream);
@ -2011,10 +2016,15 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
/* Update the initial window size of the all active streams */ /* Update the initial window size of the all active streams */
/* Check that initial_window_size < (1u << 31) */ /* Check that initial_window_size < (1u << 31) */
if(entry->value < (1u << 31)) { if(entry->value <= NGHTTP2_MAX_WINDOW_SIZE) {
rv = nghttp2_session_update_initial_window_size(session, entry->value); rv = nghttp2_session_update_initial_window_size(session, entry->value);
if(rv != 0) { if(rv != 0) {
return rv; if(nghttp2_is_fatal(rv)) {
return rv;
} else {
return nghttp2_session_handle_invalid_connection
(session, frame, NGHTTP2_FLOW_CONTROL_ERROR);
}
} }
} else { } else {
return nghttp2_session_handle_invalid_connection return nghttp2_session_handle_invalid_connection
@ -2143,7 +2153,7 @@ static int nghttp2_push_back_deferred_data_func(nghttp2_map_entry *entry,
outbound queue. */ outbound queue. */
if(stream->deferred_data && if(stream->deferred_data &&
(stream->deferred_flags & NGHTTP2_DEFERRED_FLOW_CONTROL) && (stream->deferred_flags & NGHTTP2_DEFERRED_FLOW_CONTROL) &&
(stream->remote_flow_control == 0 || stream->window_size > 0)) { (stream->remote_flow_control == 0 || stream->remote_window_size > 0)) {
int rv; int rv;
rv = nghttp2_pq_push(&session->ob_pq, stream->deferred_data); rv = nghttp2_pq_push(&session->ob_pq, stream->deferred_data);
if(rv == 0) { if(rv == 0) {
@ -2232,15 +2242,16 @@ int nghttp2_session_on_window_update_received(nghttp2_session *session,
nghttp2_session_call_on_frame_received(session, frame); nghttp2_session_call_on_frame_received(session, frame);
return 0; return 0;
} }
if(INT32_MAX - frame->window_update.window_size_increment < if(NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
stream->window_size) { stream->remote_window_size) {
int r; int r;
r = nghttp2_session_handle_invalid_stream r = nghttp2_session_handle_invalid_stream
(session, frame, NGHTTP2_FLOW_CONTROL_ERROR); (session, frame, NGHTTP2_FLOW_CONTROL_ERROR);
return r; return r;
} else { } else {
stream->window_size += frame->window_update.window_size_increment; stream->remote_window_size +=
if(stream->window_size > 0 && frame->window_update.window_size_increment;
if(stream->remote_window_size > 0 &&
(session->remote_flow_control == 0 || (session->remote_flow_control == 0 ||
session->window_size > 0) && session->window_size > 0) &&
stream->deferred_data != NULL && stream->deferred_data != NULL &&

View File

@ -31,7 +31,8 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
nghttp2_stream_state initial_state, nghttp2_stream_state initial_state,
uint8_t remote_flow_control, uint8_t remote_flow_control,
uint8_t local_flow_control, uint8_t local_flow_control,
int32_t initial_window_size, int32_t remote_initial_window_size,
int32_t local_initial_window_size,
void *stream_user_data) void *stream_user_data)
{ {
nghttp2_map_entry_init(&stream->map_entry, stream_id); nghttp2_map_entry_init(&stream->map_entry, stream_id);
@ -45,7 +46,8 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
stream->deferred_flags = NGHTTP2_DEFERRED_NONE; stream->deferred_flags = NGHTTP2_DEFERRED_NONE;
stream->remote_flow_control = remote_flow_control; stream->remote_flow_control = remote_flow_control;
stream->local_flow_control = local_flow_control; stream->local_flow_control = local_flow_control;
stream->window_size = initial_window_size; stream->remote_window_size = remote_initial_window_size;
stream->local_window_size = local_initial_window_size;
stream->recv_window_size = 0; stream->recv_window_size = 0;
} }
@ -75,12 +77,20 @@ void nghttp2_stream_detach_deferred_data(nghttp2_stream *stream)
stream->deferred_flags = NGHTTP2_DEFERRED_NONE; stream->deferred_flags = NGHTTP2_DEFERRED_NONE;
} }
void nghttp2_stream_update_initial_window_size(nghttp2_stream *stream, int nghttp2_stream_update_remote_initial_window_size
int32_t new_initial_window_size, (nghttp2_stream *stream,
int32_t old_initial_window_size) int32_t new_initial_window_size,
int32_t old_initial_window_size)
{ {
stream->window_size = int64_t new_window_size = (int64_t)stream->remote_window_size +
new_initial_window_size-(old_initial_window_size-stream->window_size); new_initial_window_size - old_initial_window_size;
if(INT32_MIN > new_window_size ||
new_window_size > NGHTTP2_MAX_WINDOW_SIZE) {
return -1;
}
stream->remote_window_size +=
new_initial_window_size - old_initial_window_size;
return 0;
} }
void nghttp2_stream_promise_fulfilled(nghttp2_stream *stream) void nghttp2_stream_promise_fulfilled(nghttp2_stream *stream)

View File

@ -109,12 +109,16 @@ typedef struct {
flow control options off or sending WINDOW_UPDATE with flow control options off or sending WINDOW_UPDATE with
END_FLOW_CONTROL bit set. */ END_FLOW_CONTROL bit set. */
uint8_t local_flow_control; uint8_t local_flow_control;
/* Current sender window size. This value is computed against the /* Current remote window size. This value is computed against the
current initial window size of remote endpoint. */ current initial window size of remote endpoint. */
int32_t window_size; int32_t remote_window_size;
/* Keep track of the number of bytes received without /* Keep track of the number of bytes received without
WINDOW_UPDATE. */ WINDOW_UPDATE. */
int32_t recv_window_size; int32_t recv_window_size;
/* window size for local window control. It is initially set to
NGHTTP2_INITIAL_WINDOW_SIZE and could be increased/decreased by
submitting WINDOW_UPDATE. See nghttp2_submit_window_update(). */
int32_t local_window_size;
} nghttp2_stream; } nghttp2_stream;
void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id, void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
@ -122,7 +126,8 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
nghttp2_stream_state initial_state, nghttp2_stream_state initial_state,
uint8_t remote_flow_control, uint8_t remote_flow_control,
uint8_t local_flow_control, uint8_t local_flow_control,
int32_t initial_window_size, int32_t remote_initial_window_size,
int32_t local_initial_window_size,
void *stream_user_data); void *stream_user_data);
void nghttp2_stream_free(nghttp2_stream *stream); void nghttp2_stream_free(nghttp2_stream *stream);
@ -149,13 +154,17 @@ void nghttp2_stream_defer_data(nghttp2_stream *stream,
void nghttp2_stream_detach_deferred_data(nghttp2_stream *stream); void nghttp2_stream_detach_deferred_data(nghttp2_stream *stream);
/* /*
* Updates the initial window size with the new value * Updates the remote window size with the new value
* |new_initial_window_size|. The |old_initial_window_size| is used to * |new_initial_window_size|. The |old_initial_window_size| is used to
* calculate the current window size. * calculate the current window size.
*
* This function returns 0 if it succeeds or -1. The failure is due to
* overflow.
*/ */
void nghttp2_stream_update_initial_window_size(nghttp2_stream *stream, int nghttp2_stream_update_remote_initial_window_size
int32_t new_initial_window_size, (nghttp2_stream *stream,
int32_t old_initial_window_size); int32_t new_initial_window_size,
int32_t old_initial_window_size);
/* /*
* Call this function if promised stream |stream| is replied with * Call this function if promised stream |stream| is replied with

View File

@ -906,8 +906,8 @@ void test_nghttp2_session_on_settings_received(void)
NGHTTP2_STREAM_OPENING, NULL); NGHTTP2_STREAM_OPENING, NULL);
/* Set window size for each streams and will see how settings /* Set window size for each streams and will see how settings
updates these values */ updates these values */
stream1->window_size = 16*1024; stream1->remote_window_size = 16*1024;
stream2->window_size = -48*1024; stream2->remote_window_size = -48*1024;
nghttp2_frame_settings_init(&frame.settings, dup_iv(iv, niv), niv); nghttp2_frame_settings_init(&frame.settings, dup_iv(iv, niv), niv);
@ -919,15 +919,15 @@ void test_nghttp2_session_on_settings_received(void)
CU_ASSERT(1 == CU_ASSERT(1 ==
session->remote_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS]); session->remote_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS]);
CU_ASSERT(64*1024 == stream1->window_size); CU_ASSERT(64*1024 == stream1->remote_window_size);
CU_ASSERT(0 == stream2->window_size); CU_ASSERT(0 == stream2->remote_window_size);
frame.settings.iv[2].value = 16*1024; frame.settings.iv[2].value = 16*1024;
CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame)); CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame));
CU_ASSERT(16*1024 == stream1->window_size); CU_ASSERT(16*1024 == stream1->remote_window_size);
CU_ASSERT(-48*1024 == stream2->window_size); CU_ASSERT(-48*1024 == stream2->remote_window_size);
CU_ASSERT(0 == stream1->remote_flow_control); CU_ASSERT(0 == stream1->remote_flow_control);
CU_ASSERT(0 == stream2->remote_flow_control); CU_ASSERT(0 == stream2->remote_flow_control);
@ -1141,7 +1141,7 @@ void test_nghttp2_session_on_window_update_received(void)
CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame)); CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame));
CU_ASSERT(1 == user_data.frame_recv_cb_called); CU_ASSERT(1 == user_data.frame_recv_cb_called);
CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE+16*1024 == stream->window_size); CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE+16*1024 == stream->remote_window_size);
data_item = malloc(sizeof(nghttp2_outbound_item)); data_item = malloc(sizeof(nghttp2_outbound_item));
memset(data_item, 0, sizeof(nghttp2_outbound_item)); memset(data_item, 0, sizeof(nghttp2_outbound_item));
@ -1150,7 +1150,8 @@ void test_nghttp2_session_on_window_update_received(void)
CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame)); CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame));
CU_ASSERT(2 == user_data.frame_recv_cb_called); CU_ASSERT(2 == user_data.frame_recv_cb_called);
CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE+16*1024*2 == stream->window_size); CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE+16*1024*2 ==
stream->remote_window_size);
CU_ASSERT(NULL == stream->deferred_data); CU_ASSERT(NULL == stream->deferred_data);
nghttp2_frame_window_update_free(&frame.window_update); nghttp2_frame_window_update_free(&frame.window_update);
@ -2475,12 +2476,12 @@ void test_nghttp2_session_flow_control(void)
/* Change initial window size to 16KiB. The window_size becomes /* Change initial window size to 16KiB. The window_size becomes
negative. */ negative. */
new_initial_window_size = 16*1024; new_initial_window_size = 16*1024;
stream->window_size = new_initial_window_size- stream->remote_window_size = new_initial_window_size-
(session->remote_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE] (session->remote_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]
-stream->window_size); - stream->remote_window_size);
session->remote_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE] = session->remote_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE] =
new_initial_window_size; new_initial_window_size;
CU_ASSERT(-48*1024 == stream->window_size); CU_ASSERT(-48*1024 == stream->remote_window_size);
/* Back 48KiB to stream window */ /* Back 48KiB to stream window */
frame.hd.stream_id = 1; frame.hd.stream_id = 1;