Apply initiated SETTINGS changes on reception of ACK
This commit is contained in:
parent
2afa9f75f5
commit
a46ccdb144
|
@ -127,6 +127,13 @@ typedef struct {
|
|||
*/
|
||||
#define NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE ((1 << 16) - 1)
|
||||
|
||||
/**
|
||||
* @macro
|
||||
*
|
||||
* The maximum header table size.
|
||||
*/
|
||||
#define NGHTTP2_MAX_HEADER_TABLE_SIZE (1 << 16)
|
||||
|
||||
/**
|
||||
* @macro
|
||||
*
|
||||
|
@ -262,6 +269,11 @@ typedef enum {
|
|||
* Callback was paused by the application
|
||||
*/
|
||||
NGHTTP2_ERR_PAUSE = -526,
|
||||
/**
|
||||
* There are too many in-flight SETTING frame and no more
|
||||
* transmission of SETTINGS is allowed.
|
||||
*/
|
||||
NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS = -527,
|
||||
/**
|
||||
* The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is
|
||||
* under unexpected condition and cannot process any further data
|
||||
|
@ -379,9 +391,9 @@ typedef enum {
|
|||
*/
|
||||
NGHTTP2_FLAG_END_PUSH_PROMISE = 0x4,
|
||||
/**
|
||||
* The PONG flag.
|
||||
* The ACK flag.
|
||||
*/
|
||||
NGHTTP2_FLAG_PONG = 0x1
|
||||
NGHTTP2_FLAG_ACK = 0x1
|
||||
} nghttp2_flag;
|
||||
|
||||
/**
|
||||
|
|
|
@ -106,12 +106,11 @@ void nghttp2_frame_rst_stream_free(nghttp2_rst_stream *frame)
|
|||
{}
|
||||
|
||||
|
||||
void nghttp2_frame_settings_init(nghttp2_settings *frame,
|
||||
void nghttp2_frame_settings_init(nghttp2_settings *frame, uint8_t flags,
|
||||
nghttp2_settings_entry *iv, size_t niv)
|
||||
{
|
||||
memset(frame, 0, sizeof(nghttp2_settings));
|
||||
nghttp2_frame_set_hd(&frame->hd, niv*8, NGHTTP2_SETTINGS, NGHTTP2_FLAG_NONE,
|
||||
0);
|
||||
nghttp2_frame_set_hd(&frame->hd, niv*8, NGHTTP2_SETTINGS, flags, 0);
|
||||
frame->niv = niv;
|
||||
frame->iv = iv;
|
||||
}
|
||||
|
|
|
@ -470,7 +470,7 @@ void nghttp2_frame_push_promise_free(nghttp2_push_promise *frame);
|
|||
* ownership of |iv|, so caller must not free it. The |flags| are
|
||||
* bitwise-OR of one or more of nghttp2_settings_flag.
|
||||
*/
|
||||
void nghttp2_frame_settings_init(nghttp2_settings *frame,
|
||||
void nghttp2_frame_settings_init(nghttp2_settings *frame, uint8_t flags,
|
||||
nghttp2_settings_entry *iv, size_t niv);
|
||||
|
||||
void nghttp2_frame_settings_free(nghttp2_settings *frame);
|
||||
|
|
|
@ -191,6 +191,8 @@ static int nghttp2_session_new(nghttp2_session **session_ptr,
|
|||
(*session_ptr)->goaway_flags = NGHTTP2_GOAWAY_NONE;
|
||||
(*session_ptr)->last_stream_id = 0;
|
||||
|
||||
(*session_ptr)->inflight_niv = -1;
|
||||
|
||||
if(server) {
|
||||
(*session_ptr)->server = 1;
|
||||
side_deflate = NGHTTP2_HD_SIDE_RESPONSE;
|
||||
|
@ -341,6 +343,7 @@ void nghttp2_session_del(nghttp2_session *session)
|
|||
if(session == NULL) {
|
||||
return;
|
||||
}
|
||||
free(session->inflight_iv);
|
||||
nghttp2_inbound_frame_reset(session);
|
||||
nghttp2_map_each_free(&session->streams, nghttp2_free_streams, NULL);
|
||||
nghttp2_session_ob_pq_free(&session->ob_pq);
|
||||
|
@ -923,6 +926,27 @@ static int nghttp2_session_predicate_window_update_send
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function checks SETTINGS can be sent at this time.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of the following
|
||||
* negative error codes:
|
||||
*
|
||||
* NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS
|
||||
* There is already another in-flight SETTINGS. Note that the
|
||||
* current implementation only allows 1 in-flight SETTINGS frame
|
||||
* without ACK flag set.
|
||||
*/
|
||||
static int nghttp2_session_predicate_settings_send(nghttp2_session *session,
|
||||
nghttp2_frame *frame)
|
||||
{
|
||||
if((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0 &&
|
||||
session->inflight_niv != -1) {
|
||||
return NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the maximum length of next data read. If the
|
||||
* connection-level and/or stream-wise flow control are enabled, the
|
||||
|
@ -1102,7 +1126,12 @@ static ssize_t nghttp2_session_prep_frame(nghttp2_session *session,
|
|||
return framebuflen;
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_SETTINGS:
|
||||
case NGHTTP2_SETTINGS: {
|
||||
int r;
|
||||
r = nghttp2_session_predicate_settings_send(session, frame);
|
||||
if(r != 0) {
|
||||
return r;
|
||||
}
|
||||
framebuflen = nghttp2_frame_pack_settings(&session->aob.framebuf,
|
||||
&session->aob.framebufmax,
|
||||
&frame->settings);
|
||||
|
@ -1110,6 +1139,7 @@ static ssize_t nghttp2_session_prep_frame(nghttp2_session *session,
|
|||
return framebuflen;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NGHTTP2_PUSH_PROMISE: {
|
||||
int r;
|
||||
nghttp2_stream *stream;
|
||||
|
@ -1412,9 +1442,29 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session)
|
|||
}
|
||||
r = 0;
|
||||
break;
|
||||
case NGHTTP2_SETTINGS:
|
||||
/* nothing to do */
|
||||
case NGHTTP2_SETTINGS: {
|
||||
size_t i;
|
||||
if(frame->hd.flags & NGHTTP2_FLAG_ACK) {
|
||||
break;
|
||||
}
|
||||
/* Only update max concurrent stream here. Applying it without
|
||||
ACK is safe because we can respond to the exceeding streams
|
||||
with REFUSED_STREAM and client will retry later. */
|
||||
for(i = frame->settings.niv; i > 0; --i) {
|
||||
if(frame->settings.iv[i - 1].settings_id ==
|
||||
NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS) {
|
||||
session->local_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] =
|
||||
frame->settings.iv[i - 1].value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(session->inflight_niv == -1);
|
||||
session->inflight_iv = frame->settings.iv;
|
||||
session->inflight_niv = frame->settings.niv;
|
||||
frame->settings.iv = NULL;
|
||||
frame->settings.niv = 0;
|
||||
break;
|
||||
}
|
||||
case NGHTTP2_PUSH_PROMISE:
|
||||
/* nothing to do */
|
||||
break;
|
||||
|
@ -2187,6 +2237,10 @@ static int nghttp2_disable_remote_flow_control_func(nghttp2_map_entry *entry,
|
|||
/*
|
||||
* Disable remote side connection-level flow control and stream-level
|
||||
* flow control of existing streams.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of negative error codes.
|
||||
*
|
||||
* The error code is always FATAL.
|
||||
*/
|
||||
static int nghttp2_session_disable_remote_flow_control
|
||||
(nghttp2_session *session)
|
||||
|
@ -2219,6 +2273,20 @@ static void nghttp2_session_disable_local_flow_control
|
|||
assert(rv == 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply SETTINGS values |iv| having |niv| elements to the local
|
||||
* settings. SETTINGS_MAX_CONCURRENT_STREAMS is not applied here
|
||||
* because it has been already applied on transmission of SETTINGS
|
||||
* frame.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of the following
|
||||
* negative error codes:
|
||||
*
|
||||
* NGHTTP2_ERR_HEADER_COMP
|
||||
* The header table size is out of range
|
||||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory
|
||||
*/
|
||||
int nghttp2_session_update_local_settings(nghttp2_session *session,
|
||||
nghttp2_settings_entry *iv,
|
||||
size_t niv)
|
||||
|
@ -2229,12 +2297,32 @@ int nghttp2_session_update_local_settings(nghttp2_session *session,
|
|||
session->local_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS];
|
||||
uint8_t new_flow_control = old_flow_control;
|
||||
int32_t new_initial_window_size = -1;
|
||||
int32_t header_table_size = -1;
|
||||
uint8_t header_table_size_seen = 0;
|
||||
/* Use the value last seen. */
|
||||
for(i = 0; i < niv; ++i) {
|
||||
if(iv[i].settings_id == NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE) {
|
||||
switch(iv[i].settings_id) {
|
||||
case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE:
|
||||
header_table_size_seen = 1;
|
||||
header_table_size = iv[i].value;
|
||||
break;
|
||||
case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
|
||||
new_initial_window_size = iv[i].value;
|
||||
} else if(iv[i].settings_id == NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS) {
|
||||
break;
|
||||
case NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS:
|
||||
new_flow_control = iv[i].value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(header_table_size_seen) {
|
||||
if(header_table_size < 0 ||
|
||||
header_table_size > NGHTTP2_MAX_HEADER_TABLE_SIZE) {
|
||||
return NGHTTP2_ERR_HEADER_COMP;
|
||||
}
|
||||
rv = nghttp2_hd_change_table_size(&session->hd_inflater,
|
||||
header_table_size);
|
||||
if(rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
if(!old_flow_control && !new_flow_control && new_initial_window_size != -1) {
|
||||
|
@ -2247,7 +2335,10 @@ int nghttp2_session_update_local_settings(nghttp2_session *session,
|
|||
}
|
||||
}
|
||||
for(i = 0; i < niv; ++i) {
|
||||
if(iv[i].settings_id > 0 && iv[i].settings_id <= NGHTTP2_SETTINGS_MAX) {
|
||||
/* SETTINGS_MAX_CONCURRENT_STREAMS has already been applied on
|
||||
transmission of the SETTINGS frame. */
|
||||
if(iv[i].settings_id > 0 && iv[i].settings_id <= NGHTTP2_SETTINGS_MAX &&
|
||||
iv[i].settings_id != NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS) {
|
||||
session->local_settings[iv[i].settings_id] = iv[i].value;
|
||||
}
|
||||
}
|
||||
|
@ -2259,7 +2350,8 @@ int nghttp2_session_update_local_settings(nghttp2_session *session,
|
|||
}
|
||||
|
||||
int nghttp2_session_on_settings_received(nghttp2_session *session,
|
||||
nghttp2_frame *frame)
|
||||
nghttp2_frame *frame,
|
||||
int noack)
|
||||
{
|
||||
int rv;
|
||||
int i;
|
||||
|
@ -2268,6 +2360,33 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
|
|||
return nghttp2_session_handle_invalid_connection(session, frame,
|
||||
NGHTTP2_PROTOCOL_ERROR);
|
||||
}
|
||||
if(frame->hd.flags & NGHTTP2_FLAG_ACK) {
|
||||
if(frame->settings.niv != 0) {
|
||||
return nghttp2_session_handle_invalid_connection
|
||||
(session, frame, NGHTTP2_FRAME_TOO_LARGE);
|
||||
}
|
||||
if(session->inflight_niv == -1) {
|
||||
return nghttp2_session_handle_invalid_connection(session, frame,
|
||||
NGHTTP2_PROTOCOL_ERROR);
|
||||
}
|
||||
rv = nghttp2_session_update_local_settings(session, session->inflight_iv,
|
||||
session->inflight_niv);
|
||||
free(session->inflight_iv);
|
||||
session->inflight_iv = NULL;
|
||||
session->inflight_niv = -1;
|
||||
if(rv != 0) {
|
||||
nghttp2_error_code error_code = NGHTTP2_INTERNAL_ERROR;
|
||||
if(nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
}
|
||||
if(rv == NGHTTP2_ERR_HEADER_COMP) {
|
||||
error_code = NGHTTP2_COMPRESSION_ERROR;
|
||||
}
|
||||
return nghttp2_session_handle_invalid_connection(session, frame,
|
||||
error_code);
|
||||
}
|
||||
return nghttp2_session_call_on_frame_received(session, frame);
|
||||
}
|
||||
/* Check ID/value pairs and persist them if necessary. */
|
||||
memset(check, 0, sizeof(check));
|
||||
for(i = (int)frame->settings.niv - 1; i >= 0; --i) {
|
||||
|
@ -2282,6 +2401,21 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
|
|||
}
|
||||
check[entry->settings_id] = 1;
|
||||
switch(entry->settings_id) {
|
||||
case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE:
|
||||
if(entry->value > NGHTTP2_MAX_HEADER_TABLE_SIZE) {
|
||||
return nghttp2_session_handle_invalid_connection
|
||||
(session, frame, NGHTTP2_COMPRESSION_ERROR);
|
||||
}
|
||||
rv = nghttp2_hd_change_table_size(&session->hd_deflater, entry->value);
|
||||
if(rv != 0) {
|
||||
if(nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
} else {
|
||||
return nghttp2_session_handle_invalid_connection
|
||||
(session, frame, NGHTTP2_COMPRESSION_ERROR);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
|
||||
/* Update the initial window size of the all active streams */
|
||||
/* Check that initial_window_size < (1u << 31) */
|
||||
|
@ -2306,6 +2440,8 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
|
|||
if(session->remote_settings[entry->settings_id] == 0) {
|
||||
rv = nghttp2_session_disable_remote_flow_control(session);
|
||||
if(rv != 0) {
|
||||
/* FATAL */
|
||||
assert(rv < NGHTTP2_ERR_FATAL);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
@ -2319,6 +2455,16 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
|
|||
}
|
||||
session->remote_settings[entry->settings_id] = entry->value;
|
||||
}
|
||||
if(!noack) {
|
||||
rv = nghttp2_session_add_settings(session, NGHTTP2_FLAG_ACK, NULL, 0);
|
||||
if(rv != 0) {
|
||||
if(nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
}
|
||||
return nghttp2_session_handle_invalid_connection
|
||||
(session, frame, NGHTTP2_INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
return nghttp2_session_call_on_frame_received(session, frame);
|
||||
}
|
||||
|
||||
|
@ -2401,9 +2547,9 @@ int nghttp2_session_on_ping_received(nghttp2_session *session,
|
|||
return nghttp2_session_handle_invalid_connection(session, frame,
|
||||
NGHTTP2_PROTOCOL_ERROR);
|
||||
}
|
||||
if((frame->hd.flags & NGHTTP2_FLAG_PONG) == 0) {
|
||||
if((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
|
||||
/* Peer sent ping, so ping it back */
|
||||
r = nghttp2_session_add_ping(session, NGHTTP2_FLAG_PONG,
|
||||
r = nghttp2_session_add_ping(session, NGHTTP2_FLAG_ACK,
|
||||
frame->ping.opaque_data);
|
||||
if(r != 0) {
|
||||
return r;
|
||||
|
@ -2645,7 +2791,7 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
|
|||
session->iframe.buf,
|
||||
session->iframe.buflen);
|
||||
if(r == 0) {
|
||||
r = nghttp2_session_on_settings_received(session, frame);
|
||||
r = nghttp2_session_on_settings_received(session, frame, 0 /* ACK */);
|
||||
if(r != NGHTTP2_ERR_PAUSE) {
|
||||
nghttp2_frame_settings_free(&frame->settings);
|
||||
}
|
||||
|
@ -3328,6 +3474,37 @@ int nghttp2_session_add_window_update(nghttp2_session *session, uint8_t flags,
|
|||
return r;
|
||||
}
|
||||
|
||||
int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
|
||||
const nghttp2_settings_entry *iv, size_t niv)
|
||||
{
|
||||
nghttp2_frame *frame;
|
||||
nghttp2_settings_entry *iv_copy;
|
||||
int r;
|
||||
if(!nghttp2_iv_check(iv, niv,
|
||||
session->local_settings
|
||||
[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS])) {
|
||||
return NGHTTP2_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
frame = malloc(sizeof(nghttp2_frame));
|
||||
if(frame == NULL) {
|
||||
return NGHTTP2_ERR_NOMEM;
|
||||
}
|
||||
iv_copy = nghttp2_frame_iv_copy(iv, niv);
|
||||
if(iv_copy == NULL) {
|
||||
free(frame);
|
||||
return NGHTTP2_ERR_NOMEM;
|
||||
}
|
||||
nghttp2_frame_settings_init(&frame->settings, flags, iv_copy, niv);
|
||||
r = nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL);
|
||||
if(r != 0) {
|
||||
/* The only expected error is fatal one */
|
||||
assert(r < NGHTTP2_ERR_FATAL);
|
||||
nghttp2_frame_settings_free(&frame->settings);
|
||||
free(frame);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
ssize_t nghttp2_session_pack_data(nghttp2_session *session,
|
||||
uint8_t **buf_ptr, size_t *buflen_ptr,
|
||||
size_t datamax,
|
||||
|
@ -3481,7 +3658,7 @@ int nghttp2_session_upgrade(nghttp2_session *session,
|
|||
memset(&frame.hd, 0, sizeof(frame.hd));
|
||||
frame.settings.iv = iv;
|
||||
frame.settings.niv = niv;
|
||||
rv = nghttp2_session_on_settings_received(session, &frame);
|
||||
rv = nghttp2_session_on_settings_received(session, &frame, 1 /* No ACK */);
|
||||
} else {
|
||||
rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, niv);
|
||||
}
|
||||
|
|
|
@ -188,6 +188,13 @@ struct nghttp2_session {
|
|||
/* Settings value of the local endpoint. */
|
||||
uint32_t local_settings[NGHTTP2_SETTINGS_MAX+1];
|
||||
|
||||
/* In-flight SETTINGS values. NULL does not necessarily mean there
|
||||
is no in-flight SETTINGS. */
|
||||
nghttp2_settings_entry *inflight_iv;
|
||||
/* The number of entries in |inflight_iv|. -1 if there is no
|
||||
in-flight SETTINGS. */
|
||||
ssize_t inflight_niv;
|
||||
|
||||
/* Option flags. This is bitwise-OR of 0 or more of nghttp2_optmask. */
|
||||
uint32_t opt_flags;
|
||||
|
||||
|
@ -295,6 +302,18 @@ int nghttp2_session_add_window_update(nghttp2_session *session, uint8_t flags,
|
|||
int32_t stream_id,
|
||||
int32_t window_size_increment);
|
||||
|
||||
/*
|
||||
* Adds SETTINGS frame.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of the following
|
||||
* negative error codes:
|
||||
*
|
||||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory.
|
||||
*/
|
||||
int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
|
||||
const nghttp2_settings_entry *iv, size_t niv);
|
||||
|
||||
/*
|
||||
* Creates new stream in |session| with stream ID |stream_id|,
|
||||
* priority |pri| and flags |flags|. NGHTTP2_FLAG_END_STREAM flag is
|
||||
|
@ -386,19 +405,33 @@ int nghttp2_session_on_priority_received(nghttp2_session *session,
|
|||
* Called when RST_STREAM is received, assuming |frame| is properly
|
||||
* initialized.
|
||||
*
|
||||
* This function returns 0 and never fail.
|
||||
* This function returns 0 if it succeeds, or one the following
|
||||
* negative error codes:
|
||||
*
|
||||
* TBD
|
||||
*/
|
||||
int nghttp2_session_on_rst_stream_received(nghttp2_session *session,
|
||||
nghttp2_frame *frame);
|
||||
|
||||
/*
|
||||
* Called when SETTINGS is received, assuming |frame| is properly
|
||||
* initialized.
|
||||
* initialized. If |noack| is non-zero, SETTINGS with ACK will not be
|
||||
* submitted. If |frame| has NGHTTP2_FLAG_ACK flag set, no SETTINGS
|
||||
* with ACK will not be submitted regardless of |noack|.
|
||||
*
|
||||
* This function returns 0 and never fail.
|
||||
* This function returns 0 if it succeeds, or one the following
|
||||
* negative error codes:
|
||||
*
|
||||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory
|
||||
* NGHTTP2_ERR_PAUSE
|
||||
* Callback function returns NGHTTP2_ERR_PAUSE
|
||||
* NGHTTP2_ERR_CALLBACK_FAILURE
|
||||
* The read_callback failed
|
||||
*/
|
||||
int nghttp2_session_on_settings_received(nghttp2_session *session,
|
||||
nghttp2_frame *frame);
|
||||
nghttp2_frame *frame,
|
||||
int noack);
|
||||
|
||||
/*
|
||||
* Called when PUSH_PROMISE is received, assuming |frame| is properly
|
||||
|
|
|
@ -209,39 +209,7 @@ int nghttp2_submit_goaway(nghttp2_session *session, uint8_t flags,
|
|||
int nghttp2_submit_settings(nghttp2_session *session, uint8_t flags,
|
||||
const nghttp2_settings_entry *iv, size_t niv)
|
||||
{
|
||||
nghttp2_frame *frame;
|
||||
nghttp2_settings_entry *iv_copy;
|
||||
int r;
|
||||
if(!nghttp2_iv_check(iv, niv,
|
||||
session->local_settings
|
||||
[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS])) {
|
||||
return NGHTTP2_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
frame = malloc(sizeof(nghttp2_frame));
|
||||
if(frame == NULL) {
|
||||
return NGHTTP2_ERR_NOMEM;
|
||||
}
|
||||
iv_copy = nghttp2_frame_iv_copy(iv, niv);
|
||||
if(iv_copy == NULL) {
|
||||
free(frame);
|
||||
return NGHTTP2_ERR_NOMEM;
|
||||
}
|
||||
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);
|
||||
if(r != 0) {
|
||||
/* The only expected error is fatal one */
|
||||
assert(r < NGHTTP2_ERR_FATAL);
|
||||
nghttp2_frame_settings_free(&frame->settings);
|
||||
free(frame);
|
||||
}
|
||||
return r;
|
||||
return nghttp2_session_add_settings(session, NGHTTP2_FLAG_NONE, iv, niv);
|
||||
}
|
||||
|
||||
int nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags,
|
||||
|
|
|
@ -80,12 +80,16 @@ namespace {
|
|||
const char* strsettingsid(int32_t id)
|
||||
{
|
||||
switch(id) {
|
||||
case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE:
|
||||
return "SETTINGS_HEADER_TABLE_SIZE";
|
||||
case NGHTTP2_SETTINGS_ENABLE_PUSH:
|
||||
return "SETTINGS_ENABLE_PUSH";
|
||||
case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
|
||||
return "MAX_CONCURRENT_STREAMS";
|
||||
return "SETTINGS_MAX_CONCURRENT_STREAMS";
|
||||
case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
|
||||
return "INITIAL_WINDOW_SIZE";
|
||||
return "SETTINGS_INITIAL_WINDOW_SIZE";
|
||||
case NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS:
|
||||
return "FLOW_CONTROL_OPTIONS";
|
||||
return "SETTINGS_FLOW_CONTROL_OPTIONS";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
@ -208,14 +212,19 @@ void print_flags(const nghttp2_frame_hd& hd)
|
|||
s += "PRIORITY";
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_SETTINGS:
|
||||
if(hd.flags & NGHTTP2_FLAG_ACK) {
|
||||
s += "ACK";
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_PUSH_PROMISE:
|
||||
if(hd.flags & NGHTTP2_FLAG_END_PUSH_PROMISE) {
|
||||
s += "END_PUSH_PROMISE";
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_PING:
|
||||
if(hd.flags & NGHTTP2_FLAG_PONG) {
|
||||
s += "PONG";
|
||||
if(hd.flags & NGHTTP2_FLAG_ACK) {
|
||||
s += "ACK";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -223,7 +223,8 @@ void test_nghttp2_frame_pack_settings()
|
|||
iv[2].settings_id = NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS;
|
||||
iv[2].value = 1;
|
||||
|
||||
nghttp2_frame_settings_init(&frame, nghttp2_frame_iv_copy(iv, 3), 3);
|
||||
nghttp2_frame_settings_init(&frame, NGHTTP2_FLAG_NONE,
|
||||
nghttp2_frame_iv_copy(iv, 3), 3);
|
||||
framelen = nghttp2_frame_pack_settings(&buf, &buflen, &frame);
|
||||
CU_ASSERT(NGHTTP2_FRAME_HEAD_LENGTH+3*8 == framelen);
|
||||
|
||||
|
@ -290,14 +291,14 @@ void test_nghttp2_frame_pack_ping(void)
|
|||
size_t buflen = 0;
|
||||
ssize_t framelen;
|
||||
const uint8_t opaque_data[] = "01234567";
|
||||
nghttp2_frame_ping_init(&frame, NGHTTP2_FLAG_PONG, opaque_data);
|
||||
nghttp2_frame_ping_init(&frame, NGHTTP2_FLAG_ACK, opaque_data);
|
||||
framelen = nghttp2_frame_pack_ping(&buf, &buflen, &frame);
|
||||
CU_ASSERT(0 == nghttp2_frame_unpack_ping
|
||||
(&oframe,
|
||||
&buf[0], NGHTTP2_FRAME_HEAD_LENGTH,
|
||||
&buf[NGHTTP2_FRAME_HEAD_LENGTH],
|
||||
framelen - NGHTTP2_FRAME_HEAD_LENGTH));
|
||||
check_frame_header(8, NGHTTP2_PING, NGHTTP2_FLAG_PONG, 0, &oframe.hd);
|
||||
check_frame_header(8, NGHTTP2_PING, NGHTTP2_FLAG_ACK, 0, &oframe.hd);
|
||||
CU_ASSERT(memcmp(opaque_data, oframe.opaque_data, sizeof(opaque_data) - 1)
|
||||
== 0);
|
||||
free(buf);
|
||||
|
|
|
@ -1134,6 +1134,7 @@ void test_nghttp2_session_on_settings_received(void)
|
|||
nghttp2_frame frame;
|
||||
const size_t niv = 5;
|
||||
nghttp2_settings_entry iv[255];
|
||||
nghttp2_outbound_item *item;
|
||||
|
||||
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
|
||||
iv[0].value = 50;
|
||||
|
@ -1166,9 +1167,10 @@ void test_nghttp2_session_on_settings_received(void)
|
|||
stream1->remote_window_size = 16*1024;
|
||||
stream2->remote_window_size = -48*1024;
|
||||
|
||||
nghttp2_frame_settings_init(&frame.settings, dup_iv(iv, niv), niv);
|
||||
nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE,
|
||||
dup_iv(iv, niv), niv);
|
||||
|
||||
CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame));
|
||||
CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
|
||||
CU_ASSERT(1000000009 ==
|
||||
session->remote_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS]);
|
||||
CU_ASSERT(64*1024 ==
|
||||
|
@ -1181,7 +1183,7 @@ void test_nghttp2_session_on_settings_received(void)
|
|||
|
||||
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, 0));
|
||||
|
||||
CU_ASSERT(16*1024 == stream1->remote_window_size);
|
||||
CU_ASSERT(-48*1024 == stream2->remote_window_size);
|
||||
|
@ -1193,6 +1195,37 @@ void test_nghttp2_session_on_settings_received(void)
|
|||
nghttp2_frame_settings_free(&frame.settings);
|
||||
|
||||
nghttp2_session_del(session);
|
||||
|
||||
/* Check ACK with niv > 0 */
|
||||
nghttp2_session_server_new(&session, &callbacks, NULL);
|
||||
nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK,
|
||||
dup_iv(iv, 1), 1);
|
||||
/* Specify inflight_iv deliberately */
|
||||
session->inflight_iv = frame.settings.iv;
|
||||
session->inflight_niv = frame.settings.niv;
|
||||
|
||||
CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
|
||||
item = nghttp2_session_get_next_ob_item(session);
|
||||
CU_ASSERT(item != NULL);
|
||||
CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item));
|
||||
|
||||
session->inflight_iv = NULL;
|
||||
session->inflight_niv = -1;
|
||||
|
||||
nghttp2_frame_settings_free(&frame.settings);
|
||||
nghttp2_session_del(session);
|
||||
|
||||
/* Check ACK against no inflight SETTINGS */
|
||||
nghttp2_session_server_new(&session, &callbacks, NULL);
|
||||
nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, NULL, 0);
|
||||
|
||||
CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
|
||||
item = nghttp2_session_get_next_ob_item(session);
|
||||
CU_ASSERT(item != NULL);
|
||||
CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item));
|
||||
|
||||
nghttp2_frame_settings_free(&frame.settings);
|
||||
nghttp2_session_del(session);
|
||||
}
|
||||
|
||||
void test_nghttp2_session_on_push_promise_received(void)
|
||||
|
@ -1346,7 +1379,7 @@ void test_nghttp2_session_on_ping_received(void)
|
|||
callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
|
||||
|
||||
nghttp2_session_client_new(&session, &callbacks, &user_data);
|
||||
nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_PONG, opaque_data);
|
||||
nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_ACK, opaque_data);
|
||||
|
||||
CU_ASSERT(0 == nghttp2_session_on_ping_received(session, &frame));
|
||||
CU_ASSERT(1 == user_data.frame_recv_cb_called);
|
||||
|
@ -1362,7 +1395,7 @@ void test_nghttp2_session_on_ping_received(void)
|
|||
CU_ASSERT(2 == user_data.frame_recv_cb_called);
|
||||
top = nghttp2_session_get_ob_pq_top(session);
|
||||
CU_ASSERT(NGHTTP2_PING == OB_CTRL_TYPE(top));
|
||||
CU_ASSERT(NGHTTP2_FLAG_PONG == OB_CTRL(top)->hd.flags);
|
||||
CU_ASSERT(NGHTTP2_FLAG_ACK == OB_CTRL(top)->hd.flags);
|
||||
CU_ASSERT(memcmp(opaque_data, OB_CTRL(top)->ping.opaque_data, 8) == 0);
|
||||
|
||||
nghttp2_frame_ping_free(&frame.ping);
|
||||
|
@ -2260,6 +2293,7 @@ void test_nghttp2_submit_settings(void)
|
|||
nghttp2_outbound_item *item;
|
||||
nghttp2_frame *frame;
|
||||
nghttp2_settings_entry iv[5];
|
||||
nghttp2_frame ack_frame;
|
||||
|
||||
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
|
||||
iv[0].value = 5;
|
||||
|
@ -2296,15 +2330,6 @@ void test_nghttp2_submit_settings(void)
|
|||
/* Now sends without 5th one */
|
||||
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 4));
|
||||
|
||||
/* Make sure that local settings are changed */
|
||||
CU_ASSERT(50 ==
|
||||
session->local_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS]);
|
||||
CU_ASSERT(16*1024 ==
|
||||
session->local_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]);
|
||||
CU_ASSERT(1 ==
|
||||
session->local_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS]);
|
||||
CU_ASSERT(0 == session->local_flow_control);
|
||||
|
||||
item = nghttp2_session_get_next_ob_item(session);
|
||||
|
||||
CU_ASSERT(NGHTTP2_SETTINGS == OB_CTRL_TYPE(item));
|
||||
|
@ -2323,6 +2348,20 @@ void test_nghttp2_submit_settings(void)
|
|||
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||
CU_ASSERT(1 == ud.frame_send_cb_called);
|
||||
|
||||
/* Only SETTINGS_MAX_CONCURRENT_STREAMS is applied on transmission */
|
||||
CU_ASSERT(50 ==
|
||||
session->local_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS]);
|
||||
|
||||
nghttp2_frame_settings_init(&ack_frame.settings, NGHTTP2_FLAG_ACK, NULL, 0);
|
||||
CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0));
|
||||
nghttp2_frame_settings_free(&ack_frame.settings);
|
||||
|
||||
CU_ASSERT(16*1024 ==
|
||||
session->local_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]);
|
||||
CU_ASSERT(1 ==
|
||||
session->local_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS]);
|
||||
CU_ASSERT(0 == session->local_flow_control);
|
||||
|
||||
nghttp2_session_del(session);
|
||||
}
|
||||
|
||||
|
@ -2333,7 +2372,9 @@ void test_nghttp2_submit_settings_update_local_window_size(void)
|
|||
nghttp2_outbound_item *item;
|
||||
nghttp2_settings_entry iv[4];
|
||||
nghttp2_stream *stream;
|
||||
my_user_data ud;
|
||||
nghttp2_frame ack_frame;
|
||||
|
||||
nghttp2_frame_settings_init(&ack_frame.settings, NGHTTP2_FLAG_ACK, NULL, 0);
|
||||
|
||||
iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
|
||||
iv[0].value = 16*1024;
|
||||
|
@ -2342,9 +2383,9 @@ void test_nghttp2_submit_settings_update_local_window_size(void)
|
|||
iv[1].value = 1;
|
||||
|
||||
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
|
||||
callbacks.send_callback = block_count_send_callback;
|
||||
callbacks.send_callback = null_send_callback;
|
||||
|
||||
nghttp2_session_server_new(&session, &callbacks, &ud);
|
||||
nghttp2_session_server_new(&session, &callbacks, NULL);
|
||||
|
||||
stream = nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE,
|
||||
NGHTTP2_PRI_DEFAULT,
|
||||
|
@ -2358,6 +2399,8 @@ void test_nghttp2_submit_settings_update_local_window_size(void)
|
|||
stream->local_flow_control = 0;
|
||||
|
||||
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1));
|
||||
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||
CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0));
|
||||
|
||||
stream = nghttp2_session_get_stream(session, 1);
|
||||
CU_ASSERT(0 == stream->recv_window_size);
|
||||
|
@ -2366,9 +2409,6 @@ void test_nghttp2_submit_settings_update_local_window_size(void)
|
|||
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);
|
||||
|
@ -2376,28 +2416,35 @@ void test_nghttp2_submit_settings_update_local_window_size(void)
|
|||
nghttp2_session_del(session);
|
||||
|
||||
/* Check flow control disabled case */
|
||||
nghttp2_session_server_new(&session, &callbacks, &ud);
|
||||
nghttp2_session_server_new(&session, &callbacks, NULL);
|
||||
stream = nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE,
|
||||
NGHTTP2_PRI_DEFAULT,
|
||||
NGHTTP2_STREAM_OPENED, NULL);
|
||||
|
||||
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 2));
|
||||
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||
CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0));
|
||||
|
||||
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);
|
||||
nghttp2_session_server_new(&session, &callbacks, NULL);
|
||||
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, NGHTTP2_FLAG_NONE, iv, 1));
|
||||
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||
CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0));
|
||||
|
||||
CU_ASSERT(NGHTTP2_STREAM_CLOSING == stream->state);
|
||||
|
||||
nghttp2_session_del(session);
|
||||
nghttp2_frame_settings_free(&ack_frame.settings);
|
||||
}
|
||||
|
||||
void test_nghttp2_submit_push_promise(void)
|
||||
|
@ -3068,8 +3115,9 @@ void test_nghttp2_session_flow_control(void)
|
|||
iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
|
||||
iv[0].value = 32*1024;
|
||||
|
||||
nghttp2_frame_settings_init(&settings_frame.settings, dup_iv(iv, 1), 1);
|
||||
nghttp2_session_on_settings_received(session, &settings_frame);
|
||||
nghttp2_frame_settings_init(&settings_frame.settings, NGHTTP2_FLAG_NONE,
|
||||
dup_iv(iv, 1), 1);
|
||||
nghttp2_session_on_settings_received(session, &settings_frame, 1);
|
||||
nghttp2_frame_settings_free(&settings_frame.settings);
|
||||
|
||||
/* Sends another 8KiB data */
|
||||
|
@ -3119,8 +3167,9 @@ void test_nghttp2_session_flow_control_disable_remote(void)
|
|||
CU_ASSERT(data_size - NGHTTP2_INITIAL_WINDOW_SIZE == ud.data_source_length);
|
||||
|
||||
/* Disable flow control entirely */
|
||||
nghttp2_frame_settings_init(&frame.settings, dup_iv(&iv, 1), 1);
|
||||
nghttp2_session_on_settings_received(session, &frame);
|
||||
nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE,
|
||||
dup_iv(&iv, 1), 1);
|
||||
nghttp2_session_on_settings_received(session, &frame, 1);
|
||||
|
||||
/* Check both connection and stream-level remote_flow_control is
|
||||
disabled */
|
||||
|
@ -3141,7 +3190,9 @@ void test_nghttp2_session_flow_control_disable_local(void)
|
|||
nghttp2_session_callbacks callbacks;
|
||||
nghttp2_stream *stream;
|
||||
nghttp2_settings_entry iv[1];
|
||||
nghttp2_frame ack_frame;
|
||||
|
||||
nghttp2_frame_settings_init(&ack_frame.settings, NGHTTP2_FLAG_ACK, NULL, 0);
|
||||
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
|
||||
callbacks.send_callback = null_send_callback;
|
||||
|
||||
|
@ -3155,11 +3206,15 @@ void test_nghttp2_session_flow_control_disable_local(void)
|
|||
iv[0].value = 1;
|
||||
|
||||
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv,
|
||||
sizeof(iv)/sizeof(iv[0])));
|
||||
ARRLEN(iv)));
|
||||
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||
CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0));
|
||||
|
||||
CU_ASSERT(0 == stream->local_flow_control);
|
||||
CU_ASSERT(0 == session->local_flow_control);
|
||||
|
||||
nghttp2_session_del(session);
|
||||
nghttp2_frame_settings_free(&ack_frame.settings);
|
||||
}
|
||||
|
||||
void test_nghttp2_session_flow_control_data_recv(void)
|
||||
|
|
Loading…
Reference in New Issue