diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 4ee107f6..e3f45837 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -3204,7 +3204,8 @@ static int adjust_recv_window_size(int32_t *recv_window_size_ptr, static int nghttp2_session_update_recv_stream_window_size (nghttp2_session *session, nghttp2_stream *stream, - int32_t delta_size) + int32_t delta_size, + int send_window_update) { int rv; rv = adjust_recv_window_size(&stream->recv_window_size, delta_size, @@ -3213,7 +3214,10 @@ static int nghttp2_session_update_recv_stream_window_size return nghttp2_session_add_rst_stream(session, stream->stream_id, NGHTTP2_FLOW_CONTROL_ERROR); } - if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE)) { + /* We don't have to send WINDOW_UPDATE if the data received is the + last chunk in the incoming stream. */ + if(send_window_update && + !(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE)) { /* We have to use local_settings here because it is the constraint the remote endpoint should honor. */ if(nghttp2_should_send_window_update(stream->local_window_size, @@ -3478,6 +3482,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, int rv; int busy = 0; nghttp2_frame_hd cont_hd; + nghttp2_stream *stream; for(;;) { switch(iframe->state) { @@ -3657,6 +3662,23 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, } iframe->frame.data.padlen = rv; iframe->state = NGHTTP2_IB_READ_DATA; + /* PAD_HIGH and PAD_LOW are subject to flow control */ + rv = nghttp2_session_update_recv_connection_window_size + (session, iframe->buflen); + if(nghttp2_is_fatal(rv)) { + return rv; + } + stream = nghttp2_session_get_stream(session, + iframe->frame.hd.stream_id); + if(stream) { + rv = nghttp2_session_update_recv_stream_window_size + (session, stream, iframe->buflen, + iframe->payloadleft || + (iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0); + if(nghttp2_is_fatal(rv)) { + return rv; + } + } break; case NGHTTP2_HEADERS: if(iframe->padlen == 0 && @@ -4037,17 +4059,15 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, if(nghttp2_is_fatal(rv)) { return rv; } - if(iframe->payloadleft || - (iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { - nghttp2_stream *stream; - stream = nghttp2_session_get_stream(session, - iframe->frame.hd.stream_id); - if(stream) { - rv = nghttp2_session_update_recv_stream_window_size - (session, stream, readlen); - if(nghttp2_is_fatal(rv)) { - return rv; - } + stream = nghttp2_session_get_stream(session, + iframe->frame.hd.stream_id); + if(stream) { + rv = nghttp2_session_update_recv_stream_window_size + (session, stream, readlen, + iframe->payloadleft || + (iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0); + if(nghttp2_is_fatal(rv)) { + return rv; } } data_readlen = inbound_frame_effective_readlen diff --git a/tests/main.c b/tests/main.c index b7d70cdf..699fb3b5 100644 --- a/tests/main.c +++ b/tests/main.c @@ -181,6 +181,8 @@ int main(int argc, char* argv[]) test_nghttp2_session_flow_control) || !CU_add_test(pSuite, "session_flow_control_data_recv", test_nghttp2_session_flow_control_data_recv) || + !CU_add_test(pSuite, "session_flow_control_data_with_padding_recv", + test_nghttp2_session_flow_control_data_with_padding_recv) || !CU_add_test(pSuite, "session_data_read_temporal_failure", test_nghttp2_session_data_read_temporal_failure) || !CU_add_test(pSuite, "session_on_stream_close", diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index c4b42e4a..22bb54b5 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -3542,6 +3542,46 @@ void test_nghttp2_session_flow_control_data_recv(void) nghttp2_session_del(session); } +void test_nghttp2_session_flow_control_data_with_padding_recv(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + uint8_t data[1024]; + nghttp2_frame_hd hd; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + /* Initial window size to 64KiB - 1*/ + nghttp2_session_client_new(&session, &callbacks, NULL); + + stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, + NGHTTP2_PRI_DEFAULT, + NGHTTP2_STREAM_OPENED, NULL); + + /* Create DATA frame */ + memset(data, 0, sizeof(data)); + hd.length = 357; + hd.type = NGHTTP2_DATA; + hd.flags = NGHTTP2_FLAG_END_STREAM | + NGHTTP2_FLAG_PAD_HIGH | NGHTTP2_FLAG_PAD_LOW;; + hd.stream_id = 1; + nghttp2_frame_pack_frame_hd(data, &hd); + /* Add 2 byte padding (PAD_LOW itself is padding) */ + data[NGHTTP2_FRAME_HEAD_LENGTH] = 1; + data[NGHTTP2_FRAME_HEAD_LENGTH + 1] = 1; + + CU_ASSERT(NGHTTP2_FRAME_HEAD_LENGTH + hd.length == + nghttp2_session_mem_recv(session, data, + NGHTTP2_FRAME_HEAD_LENGTH + hd.length)); + + CU_ASSERT(hd.length == session->recv_window_size); + CU_ASSERT(hd.length == stream->recv_window_size); + + nghttp2_session_del(session); +} + void test_nghttp2_session_data_read_temporal_failure(void) { nghttp2_session *session; diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index e608d148..626963a4 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -81,6 +81,7 @@ void test_nghttp2_session_stop_data_with_rst_stream(void); void test_nghttp2_session_defer_data(void); void test_nghttp2_session_flow_control(void); void test_nghttp2_session_flow_control_data_recv(void); +void test_nghttp2_session_flow_control_data_with_padding_recv(void); void test_nghttp2_session_data_read_temporal_failure(void); void test_nghttp2_session_on_stream_close(void); void test_nghttp2_session_on_ctrl_not_send(void);