Commit b979d2e8 authored by Tatsuhiro Tsujikawa's avatar Tatsuhiro Tsujikawa

Support increment/reduction of local window size by WINDOW_UPDATE

parent e67096fe
...@@ -1615,6 +1615,10 @@ int nghttp2_submit_rst_stream(nghttp2_session *session, int32_t stream_id, ...@@ -1615,6 +1615,10 @@ int nghttp2_submit_rst_stream(nghttp2_session *session, int32_t stream_id,
* This function does not take ownership of the |iv|. This function * This function does not take ownership of the |iv|. This function
* copies all the elements in the |iv|. * copies all the elements in the |iv|.
* *
* While updating individual stream's local window size, if the window
* size becomes strictly larger than NGHTTP2_MAX_WINDOW_SIZE,
* RST_STREAM is issued against such a stream.
*
* This function returns 0 if it succeeds, or one of the following * This function returns 0 if it succeeds, or one of the following
* negative error codes: * negative error codes:
* *
...@@ -1711,21 +1715,29 @@ int nghttp2_submit_goaway(nghttp2_session *session, ...@@ -1711,21 +1715,29 @@ int nghttp2_submit_goaway(nghttp2_session *session,
/** /**
* @function * @function
* *
* Submits WINDOW_UPDATE frame. The effective range of the * Submits WINDOW_UPDATE frame.
* |window_size_increment| is [1, (1 << 31)-1], inclusive. But the *
* application must be responsible to keep the resulting window size * If the |window_size_increment| is positive, the WINDOW_UPDATE with
* <= (1 << 31)-1. If :enum:`NGHTTP2_FLAG_END_FLOW_CONTROL` bit set in * that value as window_size_increment is queued. If the
* the |flags|, 0 can be specified in the |window_size_increment|. In * |window_size_increment| is larger than the received bytes from the
* fact, if this flag is set, the value specified in the * remote endpoint, the local window size is increased by that
* |window_size_increment| is ignored. * difference.
*
* If the |window_size_increment| is negative, the local window size
* is decreased by -|window_size_increment|. If
* :enum:`NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE` is not set and the
* library decided that the WINDOW_UPDATE should be submitted, then
* WINDOW_UPDATE is queued with the current received bytes count.
* *
* This function returns 0 if it succeeds, or one of the following * This function returns 0 if it succeeds, or one of the following
* negative error codes: * negative error codes:
* *
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
* The |delta_window_size| is 0 or negative and * The |delta_window_size| is 0 and
* :enum:`NGHTTP2_FLAG_END_FLOW_CONTROL` bit is not set in * :enum:`NGHTTP2_FLAG_END_FLOW_CONTROL` bit is not set in
* |flags|. * |flags|.
* :enum:`NGHTTP2_ERR_FLOW_CONTROL`
* The local window size overflow or gets negative.
* :enum:`NGHTTP2_ERR_STREAM_CLOSED` * :enum:`NGHTTP2_ERR_STREAM_CLOSED`
* The stream is already closed or does not exist. * The stream is already closed or does not exist.
* :enum:`NGHTTP2_ERR_NOMEM` * :enum:`NGHTTP2_ERR_NOMEM`
......
...@@ -91,6 +91,37 @@ void nghttp2_downcase(uint8_t *s, size_t len) ...@@ -91,6 +91,37 @@ void nghttp2_downcase(uint8_t *s, size_t len)
} }
} }
int nghttp2_adjust_local_window_size(int32_t *local_window_size_ptr,
int32_t *recv_window_size_ptr,
int32_t delta)
{
if(delta > 0) {
int32_t new_recv_window_size = *recv_window_size_ptr - delta;
if(new_recv_window_size < 0) {
if(*local_window_size_ptr >
NGHTTP2_MAX_WINDOW_SIZE + new_recv_window_size) {
return NGHTTP2_ERR_FLOW_CONTROL;
}
*local_window_size_ptr -= new_recv_window_size;
new_recv_window_size = 0;
}
*recv_window_size_ptr = new_recv_window_size;
return 0;
} else {
if(*local_window_size_ptr + delta < 0) {
return NGHTTP2_ERR_FLOW_CONTROL;
}
*local_window_size_ptr += delta;
}
return 0;
}
int nghttp2_should_send_window_update(int32_t local_window_size,
int32_t recv_window_size)
{
return recv_window_size >= local_window_size / 2;
}
const char* nghttp2_strerror(int error_code) const char* nghttp2_strerror(int error_code)
{ {
switch(error_code) { switch(error_code) {
......
...@@ -91,4 +91,29 @@ void* nghttp2_memdup(const void* src, size_t n); ...@@ -91,4 +91,29 @@ void* nghttp2_memdup(const void* src, size_t n);
void nghttp2_downcase(uint8_t *s, size_t len); void nghttp2_downcase(uint8_t *s, size_t len);
/*
* Adjusts |*local_window_size_ptr| and |*recv_window_size_ptr| with
* |delta| which is the WINDOW_UPDATE's window_size_increment sent
* from local side. If |delta| is strictly larger than
* |*recv_window_size_ptr|, |*local_window_size_ptr| is increased by
* delta - *recv_window_size_ptr. If |delta| is negative,
* |*local_window_size_ptr| is decreased by delta.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* NGHTTP2_ERR_FLOW_CONTROL
* local_window_size overflow or gets negative.
*/
int nghttp2_adjust_local_window_size(int32_t *local_window_size_ptr,
int32_t *recv_window_size_ptr,
int32_t delta);
/*
* Returns non-zero if the function decided that WINDOW_UPDATE should
* be sent.
*/
int nghttp2_should_send_window_update(int32_t local_window_size,
int32_t recv_window_size);
#endif /* NGHTTP2_HELPER_H */ #endif /* NGHTTP2_HELPER_H */
...@@ -146,6 +146,7 @@ static int nghttp2_session_new(nghttp2_session **session_ptr, ...@@ -146,6 +146,7 @@ static int nghttp2_session_new(nghttp2_session **session_ptr,
(*session_ptr)->local_flow_control = 1; (*session_ptr)->local_flow_control = 1;
(*session_ptr)->window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE; (*session_ptr)->window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
(*session_ptr)->recv_window_size = 0; (*session_ptr)->recv_window_size = 0;
(*session_ptr)->local_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
(*session_ptr)->goaway_flags = NGHTTP2_GOAWAY_NONE; (*session_ptr)->goaway_flags = NGHTTP2_GOAWAY_NONE;
(*session_ptr)->last_stream_id = 0; (*session_ptr)->last_stream_id = 0;
...@@ -1856,8 +1857,9 @@ int nghttp2_session_on_rst_stream_received(nghttp2_session *session, ...@@ -1856,8 +1857,9 @@ int nghttp2_session_on_rst_stream_received(nghttp2_session *session,
return 0; return 0;
} }
static int nghttp2_update_initial_window_size_func(nghttp2_map_entry *entry, static int nghttp2_update_remote_initial_window_size_func
void *ptr) (nghttp2_map_entry *entry,
void *ptr)
{ {
int rv; int rv;
nghttp2_update_window_size_arg *arg; nghttp2_update_window_size_arg *arg;
...@@ -1868,7 +1870,8 @@ static int nghttp2_update_initial_window_size_func(nghttp2_map_entry *entry, ...@@ -1868,7 +1870,8 @@ static int nghttp2_update_initial_window_size_func(nghttp2_map_entry *entry,
arg->new_window_size, arg->new_window_size,
arg->old_window_size); arg->old_window_size);
if(rv != 0) { if(rv != 0) {
return NGHTTP2_ERR_FLOW_CONTROL; return nghttp2_session_add_rst_stream(arg->session, stream->stream_id,
NGHTTP2_FLOW_CONTROL_ERROR);
} }
/* If window size gets positive, push deferred DATA frame to /* If window size gets positive, push deferred DATA frame to
outbound queue. */ outbound queue. */
...@@ -1890,8 +1893,8 @@ static int nghttp2_update_initial_window_size_func(nghttp2_map_entry *entry, ...@@ -1890,8 +1893,8 @@ static int nghttp2_update_initial_window_size_func(nghttp2_map_entry *entry,
} }
/* /*
* Updates the initial window size of all active streams. * Updates the remote initial window size of all active streams. If
* If error occurs, all streams may not be updated. * error occurs, all streams may not be updated.
* *
* This function returns 0 if it succeeds, or one of the following * This function returns 0 if it succeeds, or one of the following
* negative error codes: * negative error codes:
...@@ -1899,7 +1902,7 @@ static int nghttp2_update_initial_window_size_func(nghttp2_map_entry *entry, ...@@ -1899,7 +1902,7 @@ static int nghttp2_update_initial_window_size_func(nghttp2_map_entry *entry,
* NGHTTP2_ERR_NOMEM * NGHTTP2_ERR_NOMEM
* Out of memory. * Out of memory.
*/ */
static int nghttp2_session_update_initial_window_size static int nghttp2_session_update_remote_initial_window_size
(nghttp2_session *session, (nghttp2_session *session,
int32_t new_initial_window_size) int32_t new_initial_window_size)
{ {
...@@ -1909,7 +1912,66 @@ static int nghttp2_session_update_initial_window_size ...@@ -1909,7 +1912,66 @@ static int nghttp2_session_update_initial_window_size
arg.old_window_size = arg.old_window_size =
session->remote_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]; session->remote_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE];
return nghttp2_map_each(&session->streams, return nghttp2_map_each(&session->streams,
nghttp2_update_initial_window_size_func, nghttp2_update_remote_initial_window_size_func,
&arg);
}
static int nghttp2_update_local_initial_window_size_func
(nghttp2_map_entry *entry,
void *ptr)
{
int rv;
nghttp2_update_window_size_arg *arg;
nghttp2_stream *stream;
arg = (nghttp2_update_window_size_arg*)ptr;
stream = (nghttp2_stream*)entry;
if(!stream->local_flow_control) {
return 0;
}
rv = nghttp2_stream_update_local_initial_window_size(stream,
arg->new_window_size,
arg->old_window_size);
if(rv != 0) {
return nghttp2_session_add_rst_stream(arg->session, stream->stream_id,
NGHTTP2_FLOW_CONTROL_ERROR);
}
if(!(arg->session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
if(nghttp2_should_send_window_update(stream->local_window_size,
stream->recv_window_size)) {
rv = nghttp2_session_add_window_update(arg->session,
NGHTTP2_FLAG_NONE,
stream->stream_id,
stream->recv_window_size);
if(rv != 0) {
return rv;
}
stream->recv_window_size = 0;
}
}
return 0;
}
/*
* Updates the local initial window size of all active streams. If
* error occurs, all streams may not be updated.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* NGHTTP2_ERR_NOMEM
* Out of memory.
*/
static int nghttp2_session_update_local_initial_window_size
(nghttp2_session *session,
int32_t new_initial_window_size,
int32_t old_initial_window_size)
{
nghttp2_update_window_size_arg arg;
arg.session = session;
arg.new_window_size = new_initial_window_size;
arg.old_window_size = old_initial_window_size;
return nghttp2_map_each(&session->streams,
nghttp2_update_local_initial_window_size_func,
&arg); &arg);
} }
...@@ -1973,14 +2035,32 @@ static void nghttp2_session_disable_local_flow_control ...@@ -1973,14 +2035,32 @@ static void nghttp2_session_disable_local_flow_control
assert(rv == 0); assert(rv == 0);
} }
void nghttp2_session_update_local_settings(nghttp2_session *session, int nghttp2_session_update_local_settings(nghttp2_session *session,
nghttp2_settings_entry *iv, nghttp2_settings_entry *iv,
size_t niv) size_t niv)
{ {
int rv;
size_t i; size_t i;
uint8_t old_flow_control = uint8_t old_flow_control =
session->local_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS]; session->local_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS];
uint8_t new_flow_control = old_flow_control;
int32_t new_initial_window_size = -1;
for(i = 0; i < niv; ++i) {
if(iv[i].settings_id == NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE) {
new_initial_window_size = iv[i].value;
} else if(iv[i].settings_id == NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS) {
new_flow_control = iv[i].value;
}
}
if(!old_flow_control && !new_flow_control && new_initial_window_size != -1) {
rv = nghttp2_session_update_local_initial_window_size
(session,
new_initial_window_size,
session->local_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]);
if(rv != 0) {
return rv;
}
}
for(i = 0; i < niv; ++i) { for(i = 0; i < niv; ++i) {
assert(iv[i].settings_id > 0 && iv[i].settings_id <= NGHTTP2_SETTINGS_MAX); assert(iv[i].settings_id > 0 && iv[i].settings_id <= NGHTTP2_SETTINGS_MAX);
session->local_settings[iv[i].settings_id] = iv[i].value; session->local_settings[iv[i].settings_id] = iv[i].value;
...@@ -1989,6 +2069,7 @@ void nghttp2_session_update_local_settings(nghttp2_session *session, ...@@ -1989,6 +2069,7 @@ void nghttp2_session_update_local_settings(nghttp2_session *session,
session->local_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS]) { session->local_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS]) {
nghttp2_session_disable_local_flow_control(session); nghttp2_session_disable_local_flow_control(session);
} }
return 0;
} }
int nghttp2_session_on_settings_received(nghttp2_session *session, int nghttp2_session_on_settings_received(nghttp2_session *session,
...@@ -2017,7 +2098,8 @@ int nghttp2_session_on_settings_received(nghttp2_session *session, ...@@ -2017,7 +2098,8 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
/* 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 <= NGHTTP2_MAX_WINDOW_SIZE) { if(entry->value <= NGHTTP2_MAX_WINDOW_SIZE) {
rv = nghttp2_session_update_initial_window_size(session, entry->value); rv = nghttp2_session_update_remote_initial_window_size
(session, entry->value);
if(rv != 0) { if(rv != 0) {
if(nghttp2_is_fatal(rv)) { if(nghttp2_is_fatal(rv)) {
return rv; return rv;
...@@ -2201,7 +2283,7 @@ int nghttp2_session_on_window_update_received(nghttp2_session *session, ...@@ -2201,7 +2283,7 @@ int nghttp2_session_on_window_update_received(nghttp2_session *session,
} }
return 0; return 0;
} }
if(INT32_MAX - frame->window_update.window_size_increment < if(NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
session->window_size) { session->window_size) {
return nghttp2_session_handle_invalid_connection return nghttp2_session_handle_invalid_connection
(session, frame, NGHTTP2_FLOW_CONTROL_ERROR); (session, frame, NGHTTP2_FLOW_CONTROL_ERROR);
...@@ -2552,18 +2634,18 @@ static int nghttp2_session_process_data_frame(nghttp2_session *session) ...@@ -2552,18 +2634,18 @@ static int nghttp2_session_process_data_frame(nghttp2_session *session)
} }
} }
static int32_t adjust_recv_window_size(int32_t recv_window_size, int32_t delta) /*
* If the resulting recv_window_size is strictly larger than
* NGHTTP2_MAX_WINDOW_SIZE, return NGHTTP2_ERR_FLOW_CONTROL.
*/
static int adjust_recv_window_size(int32_t *recv_window_size_ptr,
int32_t delta)
{ {
/* If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set and the application if(*recv_window_size_ptr > NGHTTP2_MAX_WINDOW_SIZE - delta) {
does not send WINDOW_UPDATE and the remote endpoint keeps return NGHTTP2_ERR_FLOW_CONTROL;
sending data, stream->recv_window_size will eventually
overflow. */
if(recv_window_size > INT32_MAX - delta) {
recv_window_size = INT32_MAX;
} else {
recv_window_size += delta;
} }
return recv_window_size; *recv_window_size_ptr += delta;
return 0;
} }
/* /*
...@@ -2583,23 +2665,25 @@ static int nghttp2_session_update_recv_stream_window_size ...@@ -2583,23 +2665,25 @@ static int nghttp2_session_update_recv_stream_window_size
nghttp2_stream *stream, nghttp2_stream *stream,
int32_t delta_size) int32_t delta_size)
{ {
stream->recv_window_size = adjust_recv_window_size int rv;
(stream->recv_window_size, delta_size); rv = adjust_recv_window_size(&stream->recv_window_size, delta_size);
if(rv != 0) {
return nghttp2_session_add_rst_stream(session, stream->stream_id,
NGHTTP2_ERR_FLOW_CONTROL);
}
if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
/* This is just a heuristics. */
/* We have to use local_settings here because it is the constraint /* We have to use local_settings here because it is the constraint
the remote endpoint should honor. */ the remote endpoint should honor. */
if((size_t)stream->recv_window_size*2 >= if(nghttp2_should_send_window_update(stream->local_window_size,
session->local_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]) { stream->recv_window_size)) {
int r; rv = nghttp2_session_add_window_update(session,
r = nghttp2_session_add_window_update(session,
NGHTTP2_FLAG_NONE, NGHTTP2_FLAG_NONE,
stream->stream_id, stream->stream_id,
stream->recv_window_size); stream->recv_window_size);
if(r == 0) { if(rv == 0) {
stream->recv_window_size = 0; stream->recv_window_size = 0;
} else { } else {
return r; return rv;
} }
} }
} }
...@@ -2622,23 +2706,24 @@ static int nghttp2_session_update_recv_connection_window_size ...@@ -2622,23 +2706,24 @@ static int nghttp2_session_update_recv_connection_window_size
(nghttp2_session *session, (nghttp2_session *session,
int32_t delta_size) int32_t delta_size)
{ {
session->recv_window_size = adjust_recv_window_size int rv;
(session->recv_window_size, delta_size); rv = adjust_recv_window_size(&session->recv_window_size, delta_size);
if(rv != 0) {
return nghttp2_session_fail_session(session, NGHTTP2_ERR_FLOW_CONTROL);
}
if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
/* Same heuristics above */ if(nghttp2_should_send_window_update(session->local_window_size,
if((size_t)session->recv_window_size*2 >= session->recv_window_size)) {
NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE) {
int r;
/* Use stream ID 0 to update connection-level flow control /* Use stream ID 0 to update connection-level flow control
window */ window */
r = nghttp2_session_add_window_update(session, rv = nghttp2_session_add_window_update(session,
NGHTTP2_FLAG_NONE, NGHTTP2_FLAG_NONE,
0, 0,
session->recv_window_size); session->recv_window_size);
if(r == 0) { if(rv == 0) {
session->recv_window_size = 0; session->recv_window_size = 0;
} else { } else {
return r; return rv;
} }
} }
} }
......
...@@ -174,6 +174,11 @@ struct nghttp2_session { ...@@ -174,6 +174,11 @@ struct nghttp2_session {
/* 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 flow control. It is initially set to
NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE and could be
increased/decreased by submitting WINDOW_UPDATE. See
nghttp2_submit_window_update(). */
int32_t local_window_size;
/* Settings value received from the remote endpoint. We just use ID /* Settings value received from the remote endpoint. We just use ID
as index. The index = 0 is unused. */ as index. The index = 0 is unused. */
...@@ -517,9 +522,19 @@ nghttp2_outbound_item* nghttp2_session_get_next_ob_item ...@@ -517,9 +522,19 @@ nghttp2_outbound_item* nghttp2_session_get_next_ob_item
* array pointed by the |iv| is given by the |niv|. This function * array pointed by the |iv| is given by the |niv|. This function
* assumes that the all settings_id member in |iv| are in range 1 to * assumes that the all settings_id member in |iv| are in range 1 to
* NGHTTP2_SETTINGS_MAX, inclusive. * NGHTTP2_SETTINGS_MAX, inclusive.
*
* While updating individual stream's local window size, if the window
* size becomes strictly larger than NGHTTP2_MAX_WINDOW_SIZE,
* RST_STREAM is issued against such a stream.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* NGHTTP2_ERR_NOMEM
* Out of memory
*/ */
void nghttp2_session_update_local_settings(nghttp2_session *session, int nghttp2_session_update_local_settings(nghttp2_session *session,
nghttp2_settings_entry *iv, nghttp2_settings_entry *iv,
size_t niv); size_t niv);
#endif /* NGHTTP2_SESSION_H */ #endif /* NGHTTP2_SESSION_H */
...@@ -77,22 +77,41 @@ void nghttp2_stream_detach_deferred_data(nghttp2_stream *stream) ...@@ -77,22 +77,41 @@ void nghttp2_stream_detach_deferred_data(nghttp2_stream *stream)
stream->deferred_flags = NGHTTP2_DEFERRED_NONE; stream->deferred_flags = NGHTTP2_DEFERRED_NONE;
} }
int nghttp2_stream_update_remote_initial_window_size static int update_initial_window_size
(nghttp2_stream *stream, (int32_t *window_size_ptr,
int32_t new_initial_window_size, int32_t new_initial_window_size,
int32_t old_initial_window_size) int32_t old_initial_window_size)
{ {
int64_t new_window_size = (int64_t)stream->remote_window_size + int64_t new_window_size = (int64_t)(*window_size_ptr) +
new_initial_window_size - old_initial_window_size; new_initial_window_size - old_initial_window_size;
if(INT32_MIN > new_window_size || if(INT32_MIN > new_window_size ||
new_window_size > NGHTTP2_MAX_WINDOW_SIZE) { new_window_size > NGHTTP2_MAX_WINDOW_SIZE) {
return -1; return -1;
} }
stream->remote_window_size += *window_size_ptr += new_initial_window_size - old_initial_window_size;
new_initial_window_size - old_initial_window_size;
return 0; return 0;
} }
int nghttp2_stream_update_remote_initial_window_size
(nghttp2_stream *stream,
int32_t new_initial_window_size,
int32_t old_initial_window_size)
{
return update_initial_window_size(&stream->remote_window_size,
new_initial_window_size,
old_initial_window_size);
}
int nghttp2_stream_update_local_initial_window_size
(nghttp2_stream *stream,
int32_t new_initial_window_size,
int32_t old_initial_window_size)
{
return update_initial_window_size(&stream->local_window_size,
new_initial_window_size,
old_initial_window_size);
}
void nghttp2_stream_promise_fulfilled(nghttp2_stream *stream) void nghttp2_stream_promise_fulfilled(nghttp2_stream *stream)
{ {
stream->state = NGHTTP2_STREAM_OPENED; stream->state = NGHTTP2_STREAM_OPENED;
......
...@@ -115,7 +115,7 @@ typedef struct { ...@@ -115,7 +115,7 @@ typedef struct {
/* 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 /* window size for local flow control. It is initially set to
NGHTTP2_INITIAL_WINDOW_SIZE and could be increased/decreased by NGHTTP2_INITIAL_WINDOW_SIZE and could be increased/decreased by
submitting WINDOW_UPDATE. See nghttp2_submit_window_update(). */ submitting WINDOW_UPDATE. See nghttp2_submit_window_update(). */
int32_t local_window_size; int32_t local_window_size;
...@@ -166,6 +166,19 @@ int nghttp2_stream_update_remote_initial_window_size ...@@ -166,6 +166,19 @@ int nghttp2_stream_update_remote_initial_window_size
int32_t new_initial_window_size, int32_t new_initial_window_size,
int32_t old_initial_window_size); int32_t old_initial_window_size);
/*
* Updates the local window size with the new value
* |new_initial_window_size|. The |old_initial_window_size| is used to
* calculate the current window size.
*
* This function returns 0 if it succeeds or -1. The failure is due to
* overflow.
*/
int nghttp2_stream_update_local_initial_window_size
(nghttp2_stream *stream,
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
* HEADERS. This function makes the state of the |stream| to * HEADERS. This function makes the state of the |stream| to
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "nghttp2_submit.h" #include "nghttp2_submit.h"
#include <string.h> #include <string.h>
#include <assert.h>
#include "nghttp2_session.h" #include "nghttp2_session.h"
#include "nghttp2_frame.h" #include "nghttp2_frame.h"
...@@ -168,10 +169,17 @@ int nghttp2_submit_settings(nghttp2_session *session, ...@@ -168,10 +169,17 @@ int nghttp2_submit_settings(nghttp2_session *session,
} }
nghttp2_frame_iv_sort(iv_copy, niv); nghttp2_frame_iv_sort(iv_copy, niv);
nghttp2_frame_settings_init(&frame->settings, iv_copy, niv); nghttp2_frame_settings_init(&frame->settings, iv_copy, niv);
r = nghttp2_session_update_local_settings(session, iv_copy, niv);
if(r != 0) {
nghttp2_frame_settings_free(&frame->settings);
free(frame);
return r;
}
r = nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL); r = nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL);
if(r == 0) { if(r != 0) {
nghttp2_session_update_local_settings(session, iv_copy, niv); /* The only expected error is fatal one */
} else { assert(r < NGHTTP2_ERR_FATAL);
nghttp2_frame_settings_free(&frame->settings); nghttp2_frame_settings_free(&frame->settings);
free(frame); free(frame);
} }
...@@ -215,27 +223,59 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags, ...@@ -215,27 +223,59 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags,
int32_t stream_id, int32_t stream_id,
int32_t window_size_increment) int32_t window_size_increment)
{ {
int rv;
nghttp2_stream *stream; nghttp2_stream *stream;
flags &= NGHTTP2_FLAG_END_FLOW_CONTROL; flags &= NGHTTP2_FLAG_END_FLOW_CONTROL;
if(flags & NGHTTP2_FLAG_END_FLOW_CONTROL) { if(flags & NGHTTP2_FLAG_END_FLOW_CONTROL) {
window_size_increment = 0; window_size_increment = 0;
} else if(window_size_increment <= 0) { } else if(window_size_increment == 0) {
return NGHTTP2_ERR_INVALID_ARGUMENT; return NGHTTP2_ERR_INVALID_ARGUMENT;
} }
if(stream_id == 0) { if(stream_id == 0) {
return nghttp2_session_add_window_update(session, flags, stream_id, if(!session->local_flow_control) {
window_size_increment); return NGHTTP2_ERR_INVALID_ARGUMENT;
}
rv = nghttp2_adjust_local_window_size(&session->local_window_size,
&session->recv_window_size,
window_size_increment);
if(rv != 0) {
return rv;
}
if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) &&
window_size_increment < 0 &&
nghttp2_should_send_window_update(session->local_window_size,
session->recv_window_size)) {
window_size_increment = session->recv_window_size;
session->recv_window_size = 0;
}
} else { } else {
stream = nghttp2_session_get_stream(session, stream_id); stream = nghttp2_session_get_stream(session, stream_id);
if(stream) { if(stream) {
stream->recv_window_size -= nghttp2_min(window_size_increment, if(!stream->local_flow_control) {
stream->recv_window_size); return NGHTTP2_ERR_INVALID_ARGUMENT;
return nghttp2_session_add_window_update(session, flags, stream_id, }
window_size_increment); rv = nghttp2_adjust_local_window_size(&stream->local_window_size,
&stream->recv_window_size,
window_size_increment);
if(rv != 0) {
return rv;
}
if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) &&
window_size_increment < 0 &&
nghttp2_should_send_window_update(stream->local_window_size,
stream->recv_window_size)) {
window_size_increment = stream->recv_window_size;
stream->recv_window_size = 0;
}
} else { } else {
return NGHTTP2_ERR_STREAM_CLOSED; return NGHTTP2_ERR_STREAM_CLOSED;
} }
} }
if(window_size_increment > 0 || (flags & NGHTTP2_FLAG_END_FLOW_CONTROL)) {
return nghttp2_session_add_window_update(session, flags, stream_id,
window_size_increment);
}
return 0;
} }
int nghttp2_submit_request(nghttp2_session *session, int32_t pri, int nghttp2_submit_request(nghttp2_session *session, int32_t pri,
......
...@@ -35,12 +35,14 @@ OBJECTS = main.c nghttp2_pq_test.c nghttp2_map_test.c nghttp2_queue_test.c \ ...@@ -35,12 +35,14 @@ OBJECTS = main.c nghttp2_pq_test.c nghttp2_map_test.c nghttp2_queue_test.c \
nghttp2_session_test.c \ nghttp2_session_test.c \
nghttp2_hd_test.c \ nghttp2_hd_test.c \
nghttp2_npn_test.c \ nghttp2_npn_test.c \
nghttp2_gzip_test.c nghttp2_gzip_test.c \
nghttp2_helper_test.c
HFILES = nghttp2_pq_test.h nghttp2_map_test.h nghttp2_queue_test.h \ HFILES = nghttp2_pq_test.h nghttp2_map_test.h nghttp2_queue_test.h \
nghttp2_buffer_test.h nghttp2_session_test.h \ nghttp2_buffer_test.h nghttp2_session_test.h \
nghttp2_frame_test.h nghttp2_stream_test.h nghttp2_hd_test.h \ nghttp2_frame_test.h nghttp2_stream_test.h nghttp2_hd_test.h \
nghttp2_npn_test.h nghttp2_gzip_test.h nghttp2_test_helper.h nghttp2_npn_test.h nghttp2_gzip_test.h nghttp2_helper_test.h \
nghttp2_test_helper.h
main_SOURCES = $(HFILES) $(OBJECTS) main_SOURCES = $(HFILES) $(OBJECTS)
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
#include "nghttp2_hd_test.h" #include "nghttp2_hd_test.h"
#include "nghttp2_npn_test.h" #include "nghttp2_npn_test.h"
#include "nghttp2_gzip_test.h" #include "nghttp2_gzip_test.h"
#include "nghttp2_helper_test.h"
static int init_suite1(void) static int init_suite1(void)
{ {
...@@ -142,10 +143,14 @@ int main(int argc, char* argv[]) ...@@ -142,10 +143,14 @@ int main(int argc, char* argv[])
!CU_add_test(pSuite, "submit_priority", test_nghttp2_submit_priority) || !CU_add_test(pSuite, "submit_priority", test_nghttp2_submit_priority) ||
!CU_add_test(pSuite, "session_submit_settings", !CU_add_test(pSuite, "session_submit_settings",
test_nghttp2_submit_settings) || test_nghttp2_submit_settings) ||
!CU_add_test(pSuite, "session_submit_settings_update_local_window_size",
test_nghttp2_submit_settings_update_local_window_size) ||
!CU_add_test(pSuite, "session_submit_push_promise", !CU_add_test(pSuite, "session_submit_push_promise",
test_nghttp2_submit_push_promise) || test_nghttp2_submit_push_promise) ||
!CU_add_test(pSuite, "submit_window_update", !CU_add_test(pSuite, "submit_window_update",
test_nghttp2_submit_window_update) || test_nghttp2_submit_window_update) ||
!CU_add_test(pSuite, "submit_window_update_local_window_size",
test_nghttp2_submit_window_update_local_window_size) ||
!CU_add_test(pSuite, "submit_invalid_nv", !CU_add_test(pSuite, "submit_invalid_nv",
test_nghttp2_submit_invalid_nv) || test_nghttp2_submit_invalid_nv) ||
!CU_add_test(pSuite, "session_open_stream", !CU_add_test(pSuite, "session_open_stream",
...@@ -231,7 +236,9 @@ int main(int argc, char* argv[]) ...@@ -231,7 +236,9 @@ int main(int argc, char* argv[])
test_nghttp2_hd_inflate_newname_subst) || test_nghttp2_hd_inflate_newname_subst) ||
!CU_add_test(pSuite, "hd_deflate_inflate", !CU_add_test(pSuite, "hd_deflate_inflate",
test_nghttp2_hd_deflate_inflate) || test_nghttp2_hd_deflate_inflate) ||
!CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) !CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) ||
!CU_add_test(pSuite, "adjust_local_window_size",
test_nghttp2_adjust_local_window_size)
) { ) {
CU_cleanup_registry(); CU_cleanup_registry();
return CU_get_error(); return CU_get_error();
......
/*
* nghttp2 - HTTP/2.0 C Library
*
* Copyright (c) 2013 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "nghttp2_helper_test.h"
#include <CUnit/CUnit.h>
#include "nghttp2_helper.h"
void test_nghttp2_adjust_local_window_size(void)
{
int32_t local_window_size = 100;
int32_t recv_window_size = 50;
CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size,
&recv_window_size, 0));
CU_ASSERT(100 == local_window_size);
CU_ASSERT(50 == recv_window_size);
CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size,
&recv_window_size, 49));
CU_ASSERT(100 == local_window_size);
CU_ASSERT(1 == recv_window_size);
CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size,
&recv_window_size, 1));
CU_ASSERT(100 == local_window_size);
CU_ASSERT(0 == recv_window_size);
CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size,
&recv_window_size, 1));
CU_ASSERT(101 == local_window_size);
CU_ASSERT(0 == recv_window_size);
CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size,
&recv_window_size, -1));
CU_ASSERT(100 == local_window_size);
CU_ASSERT(0 == recv_window_size);
recv_window_size = 50;
CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL ==
nghttp2_adjust_local_window_size(&local_window_size,
&recv_window_size,
INT32_MAX));
CU_ASSERT(100 == local_window_size);
CU_ASSERT(50 == recv_window_size);
CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL ==
nghttp2_adjust_local_window_size(&local_window_size,
&recv_window_size,
INT32_MIN));
CU_ASSERT(100 == local_window_size);
CU_ASSERT(50 == recv_window_size);
}
/*
* nghttp2 - HTTP/2.0 C Library
*
* Copyright (c) 2013 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef NGHTTP2_HELPER_TEST_H
#define NGHTTP2_HELPER_TEST_H
void test_nghttp2_adjust_local_window_size(void);
#endif /* NGHTTP2_HELPER_TEST_H */
...@@ -1927,6 +1927,80 @@ void test_nghttp2_submit_settings(void) ...@@ -1927,6 +1927,80 @@ void test_nghttp2_submit_settings(void)
nghttp2_session_del(session); nghttp2_session_del(session);
} }
void test_nghttp2_submit_settings_update_local_window_size(void)
{
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_outbound_item *item;
nghttp2_settings_entry iv[4];
nghttp2_stream *stream;
my_user_data ud;
iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
iv[0].value = 16*1024;
iv[1].settings_id = NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS;
iv[1].value = 1;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = block_count_send_callback;
nghttp2_session_server_new(&session, &callbacks, &ud);
stream = nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_OPENED, NULL);
stream->local_window_size = NGHTTP2_INITIAL_WINDOW_SIZE + 100;
stream->recv_window_size = 32768;
stream = nghttp2_session_open_stream(session, 3, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_OPENED, NULL);
stream->local_flow_control = 0;
CU_ASSERT(0 == nghttp2_submit_settings(session, iv, 1));
stream = nghttp2_session_get_stream(session, 1);
CU_ASSERT(0 == stream->recv_window_size);
CU_ASSERT(16*1024 + 100 == stream->local_window_size);
stream = nghttp2_session_get_stream(session, 3);
CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == stream->local_window_size);
/* Setting block_count = 0 will pop first entry SETTINGS */
ud.block_count = 0;
CU_ASSERT(0 == nghttp2_session_send(session));
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_WINDOW_UPDATE == OB_CTRL_TYPE(item));
CU_ASSERT(32768 == OB_CTRL(item)->window_update.window_size_increment);
nghttp2_session_del(session);
/* Check flow control disabled case */
nghttp2_session_server_new(&session, &callbacks, &ud);
stream = nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_OPENED, NULL);
CU_ASSERT(0 == nghttp2_submit_settings(session, iv, 2));
CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == stream->local_window_size);
nghttp2_session_del(session);
/* Check overflow case */
iv[0].value = 128*1024;
nghttp2_session_server_new(&session, &callbacks, &ud);
stream = nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_OPENED, NULL);
stream->local_window_size = NGHTTP2_MAX_WINDOW_SIZE;
CU_ASSERT(0 == nghttp2_submit_settings(session, iv, 1));
CU_ASSERT(NGHTTP2_STREAM_CLOSING == stream->state);
nghttp2_session_del(session);
}
void test_nghttp2_submit_push_promise(void) void test_nghttp2_submit_push_promise(void)
{ {
nghttp2_session *session; nghttp2_session *session;
...@@ -2025,6 +2099,83 @@ void test_nghttp2_submit_window_update(void) ...@@ -2025,6 +2099,83 @@ void test_nghttp2_submit_window_update(void)
nghttp2_session_del(session); nghttp2_session_del(session);
} }
void test_nghttp2_submit_window_update_local_window_size(void)
{
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_outbound_item *item;
nghttp2_stream *stream;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback;
nghttp2_session_client_new(&session, &callbacks, NULL);
stream = nghttp2_session_open_stream(session, 2,
NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_OPENED, NULL);
stream->recv_window_size = 4096;
CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2,
stream->recv_window_size + 1));
CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1 == stream->local_window_size);
CU_ASSERT(0 == stream->recv_window_size);
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_WINDOW_UPDATE == OB_CTRL_TYPE(item));
CU_ASSERT(4097 == OB_CTRL(item)->window_update.window_size_increment);
CU_ASSERT(0 == nghttp2_session_send(session));
/* Let's decrement local window size */
stream->recv_window_size = 32768;
CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2,
-stream->local_window_size / 2));
CU_ASSERT(32768 == stream->local_window_size);
CU_ASSERT(0 == stream->recv_window_size);
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_WINDOW_UPDATE == OB_CTRL_TYPE(item));
CU_ASSERT(32768 == OB_CTRL(item)->window_update.window_size_increment);
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL ==
nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2,
NGHTTP2_MAX_WINDOW_SIZE));
CU_ASSERT(0 == nghttp2_session_send(session));
/* Check connection-level flow control */
session->recv_window_size = 4096;
CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0,
session->recv_window_size + 1));
CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1 ==
session->local_window_size);
CU_ASSERT(0 == session->recv_window_size);
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_WINDOW_UPDATE == OB_CTRL_TYPE(item));
CU_ASSERT(4097 == OB_CTRL(item)->window_update.window_size_increment);
CU_ASSERT(0 == nghttp2_session_send(session));
/* Go decrement part */
session->recv_window_size = 32768;
CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0,
-session->local_window_size/2));
CU_ASSERT(32768 == session->local_window_size);
CU_ASSERT(0 == session->recv_window_size);
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_WINDOW_UPDATE == OB_CTRL_TYPE(item));
CU_ASSERT(32768 == OB_CTRL(item)->window_update.window_size_increment);
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL ==
nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0,
NGHTTP2_MAX_WINDOW_SIZE));
nghttp2_session_del(session);
}
void test_nghttp2_submit_invalid_nv(void) void test_nghttp2_submit_invalid_nv(void)
{ {
nghttp2_session *session; nghttp2_session *session;
......
...@@ -62,8 +62,10 @@ void test_nghttp2_submit_headers_push_reply(void); ...@@ -62,8 +62,10 @@ void test_nghttp2_submit_headers_push_reply(void);
void test_nghttp2_submit_headers(void); void test_nghttp2_submit_headers(void);
void test_nghttp2_submit_priority(void); void test_nghttp2_submit_priority(void);
void test_nghttp2_submit_settings(void); void test_nghttp2_submit_settings(void);
void test_nghttp2_submit_settings_update_local_window_size(void);
void test_nghttp2_submit_push_promise(void); void test_nghttp2_submit_push_promise(void);
void test_nghttp2_submit_window_update(void); void test_nghttp2_submit_window_update(void);
void test_nghttp2_submit_window_update_local_window_size(void);
void test_nghttp2_submit_invalid_nv(void); void test_nghttp2_submit_invalid_nv(void);
void test_nghttp2_session_open_stream(void); void test_nghttp2_session_open_stream(void);
void test_nghttp2_session_get_next_ob_item(void); void test_nghttp2_session_get_next_ob_item(void);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment