Apply initiated SETTINGS changes on reception of ACK

This commit is contained in:
Tatsuhiro Tsujikawa 2013-10-27 19:22:51 +09:00
parent 2afa9f75f5
commit a46ccdb144
9 changed files with 343 additions and 89 deletions

View File

@ -127,6 +127,13 @@ typedef struct {
*/ */
#define NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE ((1 << 16) - 1) #define NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE ((1 << 16) - 1)
/**
* @macro
*
* The maximum header table size.
*/
#define NGHTTP2_MAX_HEADER_TABLE_SIZE (1 << 16)
/** /**
* @macro * @macro
* *
@ -262,6 +269,11 @@ typedef enum {
* Callback was paused by the application * Callback was paused by the application
*/ */
NGHTTP2_ERR_PAUSE = -526, 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 * 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
@ -379,9 +391,9 @@ typedef enum {
*/ */
NGHTTP2_FLAG_END_PUSH_PROMISE = 0x4, NGHTTP2_FLAG_END_PUSH_PROMISE = 0x4,
/** /**
* The PONG flag. * The ACK flag.
*/ */
NGHTTP2_FLAG_PONG = 0x1 NGHTTP2_FLAG_ACK = 0x1
} nghttp2_flag; } nghttp2_flag;
/** /**

View File

@ -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) nghttp2_settings_entry *iv, size_t niv)
{ {
memset(frame, 0, sizeof(nghttp2_settings)); memset(frame, 0, sizeof(nghttp2_settings));
nghttp2_frame_set_hd(&frame->hd, niv*8, NGHTTP2_SETTINGS, NGHTTP2_FLAG_NONE, nghttp2_frame_set_hd(&frame->hd, niv*8, NGHTTP2_SETTINGS, flags, 0);
0);
frame->niv = niv; frame->niv = niv;
frame->iv = iv; frame->iv = iv;
} }

View File

@ -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 * ownership of |iv|, so caller must not free it. The |flags| are
* bitwise-OR of one or more of nghttp2_settings_flag. * 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); nghttp2_settings_entry *iv, size_t niv);
void nghttp2_frame_settings_free(nghttp2_settings *frame); void nghttp2_frame_settings_free(nghttp2_settings *frame);

View File

@ -191,6 +191,8 @@ static int nghttp2_session_new(nghttp2_session **session_ptr,
(*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;
(*session_ptr)->inflight_niv = -1;
if(server) { if(server) {
(*session_ptr)->server = 1; (*session_ptr)->server = 1;
side_deflate = NGHTTP2_HD_SIDE_RESPONSE; side_deflate = NGHTTP2_HD_SIDE_RESPONSE;
@ -341,6 +343,7 @@ void nghttp2_session_del(nghttp2_session *session)
if(session == NULL) { if(session == NULL) {
return; return;
} }
free(session->inflight_iv);
nghttp2_inbound_frame_reset(session); nghttp2_inbound_frame_reset(session);
nghttp2_map_each_free(&session->streams, nghttp2_free_streams, NULL); nghttp2_map_each_free(&session->streams, nghttp2_free_streams, NULL);
nghttp2_session_ob_pq_free(&session->ob_pq); nghttp2_session_ob_pq_free(&session->ob_pq);
@ -923,6 +926,27 @@ static int nghttp2_session_predicate_window_update_send
return 0; 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 * Returns the maximum length of next data read. If the
* connection-level and/or stream-wise flow control are enabled, 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; return framebuflen;
} }
break; 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, framebuflen = nghttp2_frame_pack_settings(&session->aob.framebuf,
&session->aob.framebufmax, &session->aob.framebufmax,
&frame->settings); &frame->settings);
@ -1110,6 +1139,7 @@ static ssize_t nghttp2_session_prep_frame(nghttp2_session *session,
return framebuflen; return framebuflen;
} }
break; break;
}
case NGHTTP2_PUSH_PROMISE: { case NGHTTP2_PUSH_PROMISE: {
int r; int r;
nghttp2_stream *stream; nghttp2_stream *stream;
@ -1412,9 +1442,29 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session)
} }
r = 0; r = 0;
break; break;
case NGHTTP2_SETTINGS: case NGHTTP2_SETTINGS: {
/* nothing to do */ size_t i;
if(frame->hd.flags & NGHTTP2_FLAG_ACK) {
break; 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: case NGHTTP2_PUSH_PROMISE:
/* nothing to do */ /* nothing to do */
break; 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 * Disable remote side connection-level flow control and stream-level
* flow control of existing streams. * 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 static int nghttp2_session_disable_remote_flow_control
(nghttp2_session *session) (nghttp2_session *session)
@ -2219,6 +2273,20 @@ static void nghttp2_session_disable_local_flow_control
assert(rv == 0); 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, int nghttp2_session_update_local_settings(nghttp2_session *session,
nghttp2_settings_entry *iv, nghttp2_settings_entry *iv,
size_t niv) size_t niv)
@ -2229,12 +2297,32 @@ int nghttp2_session_update_local_settings(nghttp2_session *session,
session->local_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS]; session->local_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS];
uint8_t new_flow_control = old_flow_control; uint8_t new_flow_control = old_flow_control;
int32_t new_initial_window_size = -1; 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. */ /* Use the value last seen. */
for(i = 0; i < niv; ++i) { 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; 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; 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) { 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) { 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; 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, int nghttp2_session_on_settings_received(nghttp2_session *session,
nghttp2_frame *frame) nghttp2_frame *frame,
int noack)
{ {
int rv; int rv;
int i; int i;
@ -2268,6 +2360,33 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
return nghttp2_session_handle_invalid_connection(session, frame, return nghttp2_session_handle_invalid_connection(session, frame,
NGHTTP2_PROTOCOL_ERROR); 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. */ /* Check ID/value pairs and persist them if necessary. */
memset(check, 0, sizeof(check)); memset(check, 0, sizeof(check));
for(i = (int)frame->settings.niv - 1; i >= 0; --i) { 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; check[entry->settings_id] = 1;
switch(entry->settings_id) { 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: 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) */
@ -2306,6 +2440,8 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
if(session->remote_settings[entry->settings_id] == 0) { if(session->remote_settings[entry->settings_id] == 0) {
rv = nghttp2_session_disable_remote_flow_control(session); rv = nghttp2_session_disable_remote_flow_control(session);
if(rv != 0) { if(rv != 0) {
/* FATAL */
assert(rv < NGHTTP2_ERR_FATAL);
return rv; return rv;
} }
} }
@ -2319,6 +2455,16 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
} }
session->remote_settings[entry->settings_id] = entry->value; 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); 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, return nghttp2_session_handle_invalid_connection(session, frame,
NGHTTP2_PROTOCOL_ERROR); 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 */ /* 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); frame->ping.opaque_data);
if(r != 0) { if(r != 0) {
return r; return r;
@ -2645,7 +2791,7 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
session->iframe.buf, session->iframe.buf,
session->iframe.buflen); session->iframe.buflen);
if(r == 0) { 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) { if(r != NGHTTP2_ERR_PAUSE) {
nghttp2_frame_settings_free(&frame->settings); nghttp2_frame_settings_free(&frame->settings);
} }
@ -3328,6 +3474,37 @@ int nghttp2_session_add_window_update(nghttp2_session *session, uint8_t flags,
return r; 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, ssize_t nghttp2_session_pack_data(nghttp2_session *session,
uint8_t **buf_ptr, size_t *buflen_ptr, uint8_t **buf_ptr, size_t *buflen_ptr,
size_t datamax, size_t datamax,
@ -3481,7 +3658,7 @@ int nghttp2_session_upgrade(nghttp2_session *session,
memset(&frame.hd, 0, sizeof(frame.hd)); memset(&frame.hd, 0, sizeof(frame.hd));
frame.settings.iv = iv; frame.settings.iv = iv;
frame.settings.niv = niv; frame.settings.niv = niv;
rv = nghttp2_session_on_settings_received(session, &frame); rv = nghttp2_session_on_settings_received(session, &frame, 1 /* No ACK */);
} else { } else {
rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, niv); rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, niv);
} }

View File

@ -188,6 +188,13 @@ struct nghttp2_session {
/* Settings value of the local endpoint. */ /* Settings value of the local endpoint. */
uint32_t local_settings[NGHTTP2_SETTINGS_MAX+1]; 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. */ /* Option flags. This is bitwise-OR of 0 or more of nghttp2_optmask. */
uint32_t opt_flags; 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 stream_id,
int32_t window_size_increment); 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|, * Creates new stream in |session| with stream ID |stream_id|,
* priority |pri| and flags |flags|. NGHTTP2_FLAG_END_STREAM flag is * 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 * Called when RST_STREAM is received, assuming |frame| is properly
* initialized. * 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, int nghttp2_session_on_rst_stream_received(nghttp2_session *session,
nghttp2_frame *frame); nghttp2_frame *frame);
/* /*
* Called when SETTINGS is received, assuming |frame| is properly * 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, 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 * Called when PUSH_PROMISE is received, assuming |frame| is properly

View File

@ -209,39 +209,7 @@ int nghttp2_submit_goaway(nghttp2_session *session, uint8_t flags,
int nghttp2_submit_settings(nghttp2_session *session, uint8_t flags, int nghttp2_submit_settings(nghttp2_session *session, uint8_t flags,
const nghttp2_settings_entry *iv, size_t niv) const nghttp2_settings_entry *iv, size_t niv)
{ {
nghttp2_frame *frame; return nghttp2_session_add_settings(session, NGHTTP2_FLAG_NONE, iv, niv);
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;
} }
int nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags, int nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags,

View File

@ -80,12 +80,16 @@ namespace {
const char* strsettingsid(int32_t id) const char* strsettingsid(int32_t id)
{ {
switch(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: case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
return "MAX_CONCURRENT_STREAMS"; return "SETTINGS_MAX_CONCURRENT_STREAMS";
case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
return "INITIAL_WINDOW_SIZE"; return "SETTINGS_INITIAL_WINDOW_SIZE";
case NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS: case NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS:
return "FLOW_CONTROL_OPTIONS"; return "SETTINGS_FLOW_CONTROL_OPTIONS";
default: default:
return "UNKNOWN"; return "UNKNOWN";
} }
@ -208,14 +212,19 @@ void print_flags(const nghttp2_frame_hd& hd)
s += "PRIORITY"; s += "PRIORITY";
} }
break; break;
case NGHTTP2_SETTINGS:
if(hd.flags & NGHTTP2_FLAG_ACK) {
s += "ACK";
}
break;
case NGHTTP2_PUSH_PROMISE: case NGHTTP2_PUSH_PROMISE:
if(hd.flags & NGHTTP2_FLAG_END_PUSH_PROMISE) { if(hd.flags & NGHTTP2_FLAG_END_PUSH_PROMISE) {
s += "END_PUSH_PROMISE"; s += "END_PUSH_PROMISE";
} }
break; break;
case NGHTTP2_PING: case NGHTTP2_PING:
if(hd.flags & NGHTTP2_FLAG_PONG) { if(hd.flags & NGHTTP2_FLAG_ACK) {
s += "PONG"; s += "ACK";
} }
break; break;
} }

View File

@ -223,7 +223,8 @@ void test_nghttp2_frame_pack_settings()
iv[2].settings_id = NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS; iv[2].settings_id = NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS;
iv[2].value = 1; 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); framelen = nghttp2_frame_pack_settings(&buf, &buflen, &frame);
CU_ASSERT(NGHTTP2_FRAME_HEAD_LENGTH+3*8 == framelen); CU_ASSERT(NGHTTP2_FRAME_HEAD_LENGTH+3*8 == framelen);
@ -290,14 +291,14 @@ void test_nghttp2_frame_pack_ping(void)
size_t buflen = 0; size_t buflen = 0;
ssize_t framelen; ssize_t framelen;
const uint8_t opaque_data[] = "01234567"; 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); framelen = nghttp2_frame_pack_ping(&buf, &buflen, &frame);
CU_ASSERT(0 == nghttp2_frame_unpack_ping CU_ASSERT(0 == nghttp2_frame_unpack_ping
(&oframe, (&oframe,
&buf[0], NGHTTP2_FRAME_HEAD_LENGTH, &buf[0], NGHTTP2_FRAME_HEAD_LENGTH,
&buf[NGHTTP2_FRAME_HEAD_LENGTH], &buf[NGHTTP2_FRAME_HEAD_LENGTH],
framelen - 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) CU_ASSERT(memcmp(opaque_data, oframe.opaque_data, sizeof(opaque_data) - 1)
== 0); == 0);
free(buf); free(buf);

View File

@ -1134,6 +1134,7 @@ void test_nghttp2_session_on_settings_received(void)
nghttp2_frame frame; nghttp2_frame frame;
const size_t niv = 5; const size_t niv = 5;
nghttp2_settings_entry iv[255]; nghttp2_settings_entry iv[255];
nghttp2_outbound_item *item;
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
iv[0].value = 50; iv[0].value = 50;
@ -1166,9 +1167,10 @@ void test_nghttp2_session_on_settings_received(void)
stream1->remote_window_size = 16*1024; stream1->remote_window_size = 16*1024;
stream2->remote_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, 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 == CU_ASSERT(1000000009 ==
session->remote_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS]); session->remote_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS]);
CU_ASSERT(64*1024 == CU_ASSERT(64*1024 ==
@ -1181,7 +1183,7 @@ void test_nghttp2_session_on_settings_received(void)
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, 0));
CU_ASSERT(16*1024 == stream1->remote_window_size); CU_ASSERT(16*1024 == stream1->remote_window_size);
CU_ASSERT(-48*1024 == stream2->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_frame_settings_free(&frame.settings);
nghttp2_session_del(session); 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) 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; callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
nghttp2_session_client_new(&session, &callbacks, &user_data); 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(0 == nghttp2_session_on_ping_received(session, &frame));
CU_ASSERT(1 == user_data.frame_recv_cb_called); 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); CU_ASSERT(2 == user_data.frame_recv_cb_called);
top = nghttp2_session_get_ob_pq_top(session); top = nghttp2_session_get_ob_pq_top(session);
CU_ASSERT(NGHTTP2_PING == OB_CTRL_TYPE(top)); 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); CU_ASSERT(memcmp(opaque_data, OB_CTRL(top)->ping.opaque_data, 8) == 0);
nghttp2_frame_ping_free(&frame.ping); nghttp2_frame_ping_free(&frame.ping);
@ -2260,6 +2293,7 @@ void test_nghttp2_submit_settings(void)
nghttp2_outbound_item *item; nghttp2_outbound_item *item;
nghttp2_frame *frame; nghttp2_frame *frame;
nghttp2_settings_entry iv[5]; nghttp2_settings_entry iv[5];
nghttp2_frame ack_frame;
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
iv[0].value = 5; iv[0].value = 5;
@ -2296,15 +2330,6 @@ void test_nghttp2_submit_settings(void)
/* Now sends without 5th one */ /* Now sends without 5th one */
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 4)); 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); item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_SETTINGS == OB_CTRL_TYPE(item)); 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(0 == nghttp2_session_send(session));
CU_ASSERT(1 == ud.frame_send_cb_called); 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); nghttp2_session_del(session);
} }
@ -2333,7 +2372,9 @@ void test_nghttp2_submit_settings_update_local_window_size(void)
nghttp2_outbound_item *item; nghttp2_outbound_item *item;
nghttp2_settings_entry iv[4]; nghttp2_settings_entry iv[4];
nghttp2_stream *stream; 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].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
iv[0].value = 16*1024; iv[0].value = 16*1024;
@ -2342,9 +2383,9 @@ void test_nghttp2_submit_settings_update_local_window_size(void)
iv[1].value = 1; iv[1].value = 1;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); 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, stream = nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT, NGHTTP2_PRI_DEFAULT,
@ -2358,6 +2399,8 @@ void test_nghttp2_submit_settings_update_local_window_size(void)
stream->local_flow_control = 0; stream->local_flow_control = 0;
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); 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); stream = nghttp2_session_get_stream(session, 1);
CU_ASSERT(0 == stream->recv_window_size); 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); stream = nghttp2_session_get_stream(session, 3);
CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == stream->local_window_size); 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); item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_WINDOW_UPDATE == OB_CTRL_TYPE(item)); CU_ASSERT(NGHTTP2_WINDOW_UPDATE == OB_CTRL_TYPE(item));
CU_ASSERT(32768 == OB_CTRL(item)->window_update.window_size_increment); 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); nghttp2_session_del(session);
/* Check flow control disabled case */ /* 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, stream = nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT, NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_OPENED, NULL); NGHTTP2_STREAM_OPENED, NULL);
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 2)); 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); CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == stream->local_window_size);
nghttp2_session_del(session); nghttp2_session_del(session);
/* Check overflow case */ /* Check overflow case */
iv[0].value = 128*1024; 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, stream = nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT, NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_OPENED, NULL); NGHTTP2_STREAM_OPENED, NULL);
stream->local_window_size = NGHTTP2_MAX_WINDOW_SIZE; stream->local_window_size = NGHTTP2_MAX_WINDOW_SIZE;
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); 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); CU_ASSERT(NGHTTP2_STREAM_CLOSING == stream->state);
nghttp2_session_del(session); nghttp2_session_del(session);
nghttp2_frame_settings_free(&ack_frame.settings);
} }
void test_nghttp2_submit_push_promise(void) 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].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
iv[0].value = 32*1024; iv[0].value = 32*1024;
nghttp2_frame_settings_init(&settings_frame.settings, dup_iv(iv, 1), 1); nghttp2_frame_settings_init(&settings_frame.settings, NGHTTP2_FLAG_NONE,
nghttp2_session_on_settings_received(session, &settings_frame); dup_iv(iv, 1), 1);
nghttp2_session_on_settings_received(session, &settings_frame, 1);
nghttp2_frame_settings_free(&settings_frame.settings); nghttp2_frame_settings_free(&settings_frame.settings);
/* Sends another 8KiB data */ /* 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); CU_ASSERT(data_size - NGHTTP2_INITIAL_WINDOW_SIZE == ud.data_source_length);
/* Disable flow control entirely */ /* Disable flow control entirely */
nghttp2_frame_settings_init(&frame.settings, dup_iv(&iv, 1), 1); nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE,
nghttp2_session_on_settings_received(session, &frame); dup_iv(&iv, 1), 1);
nghttp2_session_on_settings_received(session, &frame, 1);
/* Check both connection and stream-level remote_flow_control is /* Check both connection and stream-level remote_flow_control is
disabled */ disabled */
@ -3141,7 +3190,9 @@ void test_nghttp2_session_flow_control_disable_local(void)
nghttp2_session_callbacks callbacks; nghttp2_session_callbacks callbacks;
nghttp2_stream *stream; nghttp2_stream *stream;
nghttp2_settings_entry iv[1]; 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)); memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback; callbacks.send_callback = null_send_callback;
@ -3155,11 +3206,15 @@ void test_nghttp2_session_flow_control_disable_local(void)
iv[0].value = 1; iv[0].value = 1;
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 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 == stream->local_flow_control);
CU_ASSERT(0 == session->local_flow_control); CU_ASSERT(0 == session->local_flow_control);
nghttp2_session_del(session); nghttp2_session_del(session);
nghttp2_frame_settings_free(&ack_frame.settings);
} }
void test_nghttp2_session_flow_control_data_recv(void) void test_nghttp2_session_flow_control_data_recv(void)