Implement DATA frame padding

This commit is contained in:
Tatsuhiro Tsujikawa 2014-02-07 23:22:17 +09:00
parent f26270b5b4
commit 814d0f76f3
13 changed files with 354 additions and 53 deletions

View File

@ -147,6 +147,14 @@ typedef struct {
*/
#define NGHTTP2_CLIENT_CONNECTION_HEADER_LEN 24
/**
* @macro
*
* The default value of DATA padding alignment. See
* :member:`NGHTTP2_OPT_DATA_PAD_ALIGNMENT`.
*/
#define NGHTTP2_DATA_PAD_ALIGNMENT 256
/**
* @enum
*
@ -599,6 +607,11 @@ typedef struct {
*/
typedef struct {
nghttp2_frame_hd hd;
/**
* The length of the padding in this frame. This includes PAD_HIGH
* and PAD_LOW.
*/
size_t padlen;
} nghttp2_data;
/**
@ -1309,7 +1322,15 @@ typedef enum {
* will be overwritten if the local endpoint receives
* SETTINGS_MAX_CONCURRENT_STREAMS from the remote endpoint.
*/
NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 2
NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 2,
/**
* This option specifies the alignment of padding in DATA frame. If
* this option is set to N, padding is added to DATA payload so that
* its payload length is divisible by N. Due to flow control,
* padding is not always added according to this alignment. The
* option value must be greater than or equal to 8.
*/
NGHTTP2_OPT_DATA_PAD_ALIGNMENT = 1 << 3
} nghttp2_opt;
/**
@ -1330,6 +1351,10 @@ typedef struct {
* :enum:`NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE`
*/
uint8_t no_auto_connection_window_update;
/**
* :enum:`NGHTTP2_OPT_DATA_PAD_ALIGNMENT`
*/
uint16_t data_pad_alignment;
} nghttp2_opt_set;
/**

View File

@ -185,6 +185,7 @@ void nghttp2_frame_window_update_free(nghttp2_window_update *frame)
void nghttp2_frame_data_init(nghttp2_data *frame, nghttp2_private_data *pdata)
{
frame->hd = pdata->hd;
frame->padlen = pdata->padlen;
/* flags may have NGHTTP2_FLAG_END_STREAM even if the sent chunk
is not the end of the stream */
if(!pdata->eof) {
@ -192,6 +193,13 @@ void nghttp2_frame_data_init(nghttp2_data *frame, nghttp2_private_data *pdata)
}
}
size_t nghttp2_frame_data_trail_padlen(nghttp2_data *frame)
{
return frame->padlen
- ((frame->hd.flags & NGHTTP2_FLAG_PAD_HIGH) > 0)
- ((frame->hd.flags & NGHTTP2_FLAG_PAD_LOW) > 0);
}
void nghttp2_frame_private_data_init(nghttp2_private_data *frame,
uint8_t flags,
int32_t stream_id,

View File

@ -71,6 +71,11 @@ typedef struct {
* The data to be sent for this DATA frame.
*/
nghttp2_data_provider data_prd;
/**
* The number of bytes added as padding. This includes PAD_HIGH and
* PAD_LOW.
*/
size_t padlen;
/**
* The flag to indicate whether EOF was reached or not. Initially
* |eof| is 0. It becomes 1 after all data were read. This is used
@ -421,6 +426,12 @@ void nghttp2_frame_window_update_free(nghttp2_window_update *frame);
void nghttp2_frame_data_init(nghttp2_data *frame, nghttp2_private_data *pdata);
/*
* Returns the number of padding data after application data
* payload. Thus this does not include the PAD_HIGH and PAD_LOW.
*/
size_t nghttp2_frame_data_trail_padlen(nghttp2_data *frame);
void nghttp2_frame_private_data_init(nghttp2_private_data *frame,
uint8_t flags,
int32_t stream_id,

View File

@ -223,6 +223,12 @@ static int nghttp2_session_new(nghttp2_session **session_ptr,
(*session_ptr)->opt_flags |=
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE;
}
if((opt_set_mask & NGHTTP2_OPT_DATA_PAD_ALIGNMENT) &&
opt_set->data_pad_alignment >= 8) {
(*session_ptr)->data_pad_alignment = opt_set->data_pad_alignment;
} else {
(*session_ptr)->data_pad_alignment = NGHTTP2_DATA_PAD_ALIGNMENT;
}
(*session_ptr)->remote_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
(*session_ptr)->recv_window_size = 0;
@ -1271,6 +1277,7 @@ static ssize_t nghttp2_session_prep_frame(nghttp2_session *session,
framebuflen = nghttp2_session_pack_data(session,
&session->aob.framebuf,
&session->aob.framebufmax,
&session->aob.framebufoff,
next_readmax,
data_frame);
if(framebuflen == NGHTTP2_ERR_DEFERRED) {
@ -1589,8 +1596,20 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session)
} else if(item->frame_cat == NGHTTP2_CAT_DATA) {
nghttp2_private_data *data_frame;
nghttp2_outbound_item* next_item;
nghttp2_stream *stream;
size_t effective_payloadlen;
data_frame = nghttp2_outbound_item_get_data_frame(session->aob.item);
stream = nghttp2_session_get_stream(session, data_frame->hd.stream_id);
/* We update flow control window after a frame was completely
sent. This is possible because we choose payload length not to
exceed the window */
effective_payloadlen = data_frame->hd.length - data_frame->padlen;
session->remote_window_size -= effective_payloadlen;
if(stream) {
stream->remote_window_size -= effective_payloadlen;
}
if(session->callbacks.on_frame_send_callback) {
nghttp2_frame public_data_frame;
nghttp2_frame_data_init(&public_data_frame.data, data_frame);
@ -1599,15 +1618,13 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session)
return rv;
}
}
if(data_frame->eof && (data_frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
nghttp2_stream *stream =
nghttp2_session_get_stream(session, data_frame->hd.stream_id);
if(stream) {
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
if(nghttp2_is_fatal(rv)) {
return rv;
}
if(stream && data_frame->eof &&
(data_frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
if(nghttp2_is_fatal(rv)) {
return rv;
}
}
/* If session is closed or RST_STREAM was queued, we won't send
@ -1618,16 +1635,14 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session)
nghttp2_active_outbound_item_reset(&session->aob);
return 0;
}
/* Assuming stream is not NULL */
assert(stream);
next_item = nghttp2_session_get_next_ob_item(session);
/* If priority of this stream is higher or equal to other stream
waiting at the top of the queue, we continue to send this
data. */
if(next_item == NULL || session->aob.item->pri < next_item->pri) {
size_t next_readmax;
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, data_frame->hd.stream_id);
/* Assuming stream is not NULL */
assert(stream);
next_readmax = nghttp2_session_next_data_read(session, stream);
if(next_readmax == 0) {
nghttp2_stream_defer_data(stream, session->aob.item,
@ -1639,6 +1654,7 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session)
rv = nghttp2_session_pack_data(session,
&session->aob.framebuf,
&session->aob.framebufmax,
&session->aob.framebufoff,
next_readmax,
data_frame);
if(nghttp2_is_fatal(rv)) {
@ -1666,7 +1682,6 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session)
}
assert(rv >= 0);
session->aob.framebuflen = session->aob.framebufmark = rv;
session->aob.framebufoff = 0;
return 0;
}
/* Update seq to interleave other streams with the same
@ -1740,6 +1755,7 @@ int nghttp2_session_send(nghttp2_session *session)
if(item->frame_cat == NGHTTP2_CAT_CTRL) {
nghttp2_frame *frame = nghttp2_outbound_item_get_ctrl_frame(item);
session->aob.framebufmark =
session->aob.framebufoff +
frame->hd.length + NGHTTP2_FRAME_HEAD_LENGTH;
r = session_call_before_frame_send(session, frame);
if(nghttp2_is_fatal(r)) {
@ -1748,10 +1764,13 @@ int nghttp2_session_send(nghttp2_session *session)
} else {
nghttp2_private_data *frame;
frame = nghttp2_outbound_item_get_data_frame(session->aob.item);
/* session->aob.framebufmark = session->aob.framebuflen; */
session->aob.framebufmark =
session->aob.framebufoff +
frame->hd.length + NGHTTP2_FRAME_HEAD_LENGTH;
}
}
data = session->aob.framebuf + session->aob.framebufoff;
datalen = session->aob.framebufmark - session->aob.framebufoff;
sentlen = session->callbacks.send_callback(session, data, datalen, 0,
@ -1763,23 +1782,6 @@ int nghttp2_session_send(nghttp2_session *session)
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
} else {
if(session->aob.item->frame_cat == NGHTTP2_CAT_DATA &&
session->aob.framebufoff + sentlen > NGHTTP2_FRAME_HEAD_LENGTH) {
nghttp2_private_data *frame;
nghttp2_stream *stream;
uint16_t len;
if(session->aob.framebufoff < NGHTTP2_FRAME_HEAD_LENGTH) {
len = session->aob.framebufoff + sentlen - NGHTTP2_FRAME_HEAD_LENGTH;
} else {
len = sentlen;
}
frame = nghttp2_outbound_item_get_data_frame(session->aob.item);
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if(stream) {
stream->remote_window_size -= len;
}
session->remote_window_size -= len;
}
session->aob.framebufoff += sentlen;
if(session->aob.framebufoff == session->aob.framebufmark) {
/* Frame has completely sent */
@ -3338,6 +3340,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
switch(iframe->frame.hd.type) {
case NGHTTP2_DATA: {
DEBUGF(fprintf(stderr, "DATA\n"));
iframe->frame.data.padlen = 0;
/* Check stream is open. If it is not open or closing,
ignore payload. */
busy = 1;
@ -3351,6 +3354,25 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
if(nghttp2_is_fatal(rv)) {
return rv;
}
if(iframe->frame.hd.flags & NGHTTP2_FLAG_PAD_HIGH) {
if((iframe->frame.hd.flags & NGHTTP2_FLAG_PAD_LOW) == 0) {
iframe->state = NGHTTP2_IB_IGN_DATA;
rv = nghttp2_session_terminate_session(session,
NGHTTP2_PROTOCOL_ERROR);
if(nghttp2_is_fatal(rv)) {
return rv;
}
break;
}
iframe->state = NGHTTP2_IB_READ_NBYTE;
iframe->left = 2;
break;
}
if(iframe->frame.hd.flags & NGHTTP2_FLAG_PAD_LOW) {
iframe->state = NGHTTP2_IB_READ_NBYTE;
iframe->left = 1;
break;
}
iframe->state = NGHTTP2_IB_READ_DATA;
break;
}
@ -3454,7 +3476,26 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
}
switch(iframe->frame.hd.type) {
case NGHTTP2_DATA:
assert(0);
busy = 1;
iframe->frame.data.padlen = iframe->buf[0];
if(iframe->frame.hd.flags & NGHTTP2_FLAG_PAD_HIGH) {
iframe->frame.data.padlen <<= 8;
iframe->frame.data.padlen |= iframe->buf[1];
++iframe->frame.data.padlen;
}
++iframe->frame.data.padlen;
DEBUGF(fprintf(stderr, "padlen=%zu\n", iframe->frame.data.padlen));
if(iframe->frame.data.padlen > iframe->frame.hd.length) {
rv = nghttp2_session_terminate_session(session,
NGHTTP2_PROTOCOL_ERROR);
if(nghttp2_is_fatal(rv)) {
return rv;
}
iframe->state = NGHTTP2_IB_IGN_DATA;
break;
}
iframe->state = NGHTTP2_IB_READ_DATA;
break;
case NGHTTP2_HEADERS:
rv = session_process_headers_frame(session);
@ -3703,6 +3744,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
DEBUGF(fprintf(stderr, "readlen=%zu, payloadleft=%zu\n",
readlen, iframe->payloadleft));
if(readlen > 0) {
size_t data_readlen = readlen;
rv = nghttp2_session_update_recv_connection_window_size
(session, readlen);
if(nghttp2_is_fatal(rv)) {
@ -3721,13 +3763,25 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
}
}
}
if(session->callbacks.on_data_chunk_recv_callback) {
if(nghttp2_frame_data_trail_padlen(&iframe->frame.data) >
iframe->payloadleft) {
size_t trail_padlen;
trail_padlen = nghttp2_frame_data_trail_padlen(&iframe->frame.data)
- iframe->payloadleft;
if(readlen < trail_padlen) {
data_readlen = 0;
} else {
data_readlen -= trail_padlen;
}
}
DEBUGF(fprintf(stderr, "data_readlen=%zu\n", data_readlen));
if(data_readlen > 0 && session->callbacks.on_data_chunk_recv_callback) {
rv = session->callbacks.on_data_chunk_recv_callback
(session,
iframe->frame.hd.flags,
iframe->frame.hd.stream_id,
in - readlen,
readlen,
data_readlen,
session->user_data);
if(rv == NGHTTP2_ERR_PAUSE) {
/* Set type to NGHTTP2_DATA, so that we can see what was
@ -3937,10 +3991,18 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
ssize_t nghttp2_session_pack_data(nghttp2_session *session,
uint8_t **buf_ptr, size_t *buflen_ptr,
size_t *bufoff_ptr,
size_t datamax,
nghttp2_private_data *frame)
{
ssize_t framelen = datamax+8, r;
/* extra 2 bytes for PAD_HIGH and PAD_LOW. We allocate extra 2 bytes
for padding. Based on the padding length, we adjust the starting
offset of frame data. The starting offset is assigned into
|*bufoff_ptr|. */
size_t headoff = 2;
size_t dataoff = NGHTTP2_FRAME_HEAD_LENGTH + headoff;
ssize_t framelen = dataoff + datamax;
ssize_t r;
int eof_flags;
uint8_t flags;
r = nghttp2_reserve_buffer(buf_ptr, buflen_ptr, framelen);
@ -3949,7 +4011,7 @@ ssize_t nghttp2_session_pack_data(nghttp2_session *session,
}
eof_flags = 0;
r = frame->data_prd.read_callback
(session, frame->hd.stream_id, (*buf_ptr)+8, datamax,
(session, frame->hd.stream_id, (*buf_ptr) + dataoff, datamax,
&eof_flags, &frame->data_prd.source, session->user_data);
if(r == NGHTTP2_ERR_DEFERRED || r == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
return r;
@ -3957,19 +4019,56 @@ ssize_t nghttp2_session_pack_data(nghttp2_session *session,
/* This is the error code when callback is failed. */
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
frame->hd.length = r;
memset(*buf_ptr, 0, NGHTTP2_FRAME_HEAD_LENGTH);
nghttp2_put_uint16be(&(*buf_ptr)[0], r);
/* Clear flags, because this may contain previous flags of previous
DATA */
frame->hd.flags &= ~(NGHTTP2_FLAG_PAD_HIGH | NGHTTP2_FLAG_PAD_LOW);
flags = 0;
if((session->opt_flags & NGHTTP2_OPTMASK_NO_DATA_PADDING) == 0 &&
r > 0 && (size_t)r < datamax) {
const size_t align = session->data_pad_alignment;
size_t nextlen = nghttp2_min((r + align - 1) / align * align, datamax);
size_t padlen = nextlen - r;
size_t trail_padlen = 0;
if(padlen > 257) {
headoff = 0;
trail_padlen = padlen - 2;
flags |= NGHTTP2_FLAG_PAD_HIGH | NGHTTP2_FLAG_PAD_LOW;
(*buf_ptr)[NGHTTP2_FRAME_HEAD_LENGTH] = trail_padlen >> 8;
(*buf_ptr)[NGHTTP2_FRAME_HEAD_LENGTH + 1] = trail_padlen & 0xff;
} else if(padlen > 0) {
headoff = 1;
trail_padlen = padlen - 1;
flags |= NGHTTP2_FLAG_PAD_LOW;
(*buf_ptr)[NGHTTP2_FRAME_HEAD_LENGTH + 1] = trail_padlen;
}
frame->padlen = padlen;
memset((*buf_ptr) + dataoff + r, 0, trail_padlen);
frame->hd.length = nextlen;
} else {
frame->padlen = 0;
frame->hd.length = r;
}
/* Set PAD flags so that we can supply frame to the callback with
the correct flags */
frame->hd.flags |= flags;
memset(*buf_ptr + headoff, 0, NGHTTP2_FRAME_HEAD_LENGTH);
nghttp2_put_uint16be(&(*buf_ptr)[headoff], frame->hd.length);
if(eof_flags) {
frame->eof = 1;
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
flags |= NGHTTP2_FLAG_END_STREAM;
}
}
(*buf_ptr)[3] = flags;
nghttp2_put_uint32be(&(*buf_ptr)[4], frame->hd.stream_id);
return r+8;
(*buf_ptr)[headoff + 3] = flags;
nghttp2_put_uint32be(&(*buf_ptr)[headoff + 4], frame->hd.stream_id);
*bufoff_ptr = headoff;
return frame->hd.length + NGHTTP2_FRAME_HEAD_LENGTH + headoff;
}
void* nghttp2_session_get_stream_user_data(nghttp2_session *session,

View File

@ -44,7 +44,10 @@
*/
typedef enum {
NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE = 1 << 0,
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE = 1 << 1
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE = 1 << 1,
/* Option to disable DATA frame padding, which is currently hidden
from outside, but provided for ease of testing */
NGHTTP2_OPTMASK_NO_DATA_PADDING = 1 << 2,
} nghttp2_optmask;
typedef struct {
@ -149,6 +152,8 @@ struct nghttp2_session {
size_t num_incoming_streams;
/* The number of bytes allocated for nvbuf */
size_t nvbuflen;
/* DATA padding alignemnt. See NGHTTP2_OPT_DATA_PAD_ALIGNMENT. */
size_t data_pad_alignment;
/* Next Stream ID. Made unsigned int to detect >= (1 << 31). */
uint32_t next_stream_id;
/* The largest stream ID received so far */
@ -508,9 +513,11 @@ nghttp2_stream* nghttp2_session_get_stream(nghttp2_session *session,
* Packs DATA frame |frame| in wire frame format and stores it in
* |*buf_ptr|. The capacity of |*buf_ptr| is |*buflen_ptr|
* length. This function expands |*buf_ptr| as necessary to store
* given |frame|. It packs header in first 8 bytes. Remaining bytes
* are the DATA apyload and are filled using |frame->data_prd|. The
* length of payload is at most |datamax| bytes.
* given |frame|. It packs header in first 8 bytes starting
* |*bufoff_ptr| offset. The |*bufoff_ptr| is calculated based on
* usage of padding. Remaining bytes are the DATA apyload and are
* filled using |frame->data_prd|. The length of payload is at most
* |datamax| bytes.
*
* This function returns the size of packed frame if it succeeds, or
* one of the following negative error codes:
@ -526,6 +533,7 @@ nghttp2_stream* nghttp2_session_get_stream(nghttp2_session *session,
*/
ssize_t nghttp2_session_pack_data(nghttp2_session *session,
uint8_t **buf_ptr, size_t *buflen_ptr,
size_t *bufoff_ptr,
size_t datamax,
nghttp2_private_data *frame);

View File

@ -66,6 +66,7 @@ const std::string NGHTTPD_SERVER = "nghttpd nghttp2/" NGHTTP2_VERSION;
Config::Config()
: data_ptr(nullptr),
output_upper_thres(1024*1024),
data_pad_alignment(NGHTTP2_DATA_PAD_ALIGNMENT),
header_table_size(-1),
port(0),
verbose(false),
@ -361,8 +362,14 @@ int Http2Handler::on_connect()
{
int r;
nghttp2_session_callbacks callbacks;
nghttp2_opt_set opt_set;
memset(&opt_set, 0, sizeof(opt_set));
opt_set.data_pad_alignment = sessions_->get_config()->data_pad_alignment;
fill_callback(callbacks, sessions_->get_config());
r = nghttp2_session_server_new(&session_, &callbacks, this);
r = nghttp2_session_server_new2(&session_, &callbacks, this,
NGHTTP2_OPT_DATA_PAD_ALIGNMENT, &opt_set);
if(r != 0) {
return r;
}

View File

@ -56,6 +56,7 @@ struct Config {
std::string cert_file;
void *data_ptr;
size_t output_upper_thres;
size_t data_pad_alignment;
ssize_t header_table_size;
uint16_t port;
bool verbose;

View File

@ -77,6 +77,8 @@ const char* strstatus(nghttp2_error_code error_code)
return "CONNECT_ERROR";
case NGHTTP2_ENHANCE_YOUR_CALM:
return "ENHANCE_YOUR_CALM";
case NGHTTP2_INADEQUATE_SECURITY:
return "INADEQUATE_SECURITY";
default:
return "UNKNOWN";
}
@ -201,11 +203,35 @@ void print_flags(const nghttp2_frame_hd& hd)
if(hd.flags & NGHTTP2_FLAG_END_STREAM) {
s += "END_STREAM";
}
if(hd.flags & NGHTTP2_FLAG_END_SEGMENT) {
if(!s.empty()) {
s += " | ";
}
s += "END_SEGMENT";
}
if(hd.flags & NGHTTP2_FLAG_PAD_LOW) {
if(!s.empty()) {
s += " | ";
}
s += "PAD_LOW";
}
if(hd.flags & NGHTTP2_FLAG_PAD_HIGH) {
if(!s.empty()) {
s += " | ";
}
s += "PAD_HIGH";
}
break;
case NGHTTP2_HEADERS:
if(hd.flags & NGHTTP2_FLAG_END_STREAM) {
s += "END_STREAM";
}
if(hd.flags & NGHTTP2_FLAG_END_SEGMENT) {
if(!s.empty()) {
s += " | ";
}
s += "END_SEGMENT";
}
if(hd.flags & NGHTTP2_FLAG_END_HEADERS) {
if(!s.empty()) {
s += " | ";
@ -265,6 +291,10 @@ void print_frame(print_type ptype, const nghttp2_frame *frame)
}
switch(frame->hd.type) {
case NGHTTP2_DATA:
if(frame->hd.flags & (NGHTTP2_FLAG_PAD_HIGH | NGHTTP2_FLAG_PAD_LOW)) {
print_frame_attr_indent();
printf("(padlen=%zu)\n", frame->data.padlen);
}
break;
case NGHTTP2_HEADERS:
if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY) {

View File

@ -82,6 +82,7 @@ struct Config {
std::string keyfile;
std::string datafile;
size_t output_upper_thres;
size_t data_pad_alignment;
ssize_t peer_max_concurrent_streams;
ssize_t header_table_size;
int32_t pri;
@ -98,6 +99,7 @@ struct Config {
bool upgrade;
Config()
: output_upper_thres(1024*1024),
data_pad_alignment(NGHTTP2_DATA_PAD_ALIGNMENT),
peer_max_concurrent_streams(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS),
header_table_size(-1),
pri(NGHTTP2_PRI_DEFAULT),
@ -712,8 +714,10 @@ struct HttpClient {
}
nghttp2_opt_set opt_set;
opt_set.peer_max_concurrent_streams = config.peer_max_concurrent_streams;
opt_set.data_pad_alignment = config.data_pad_alignment;
rv = nghttp2_session_client_new2(&session, callbacks, this,
NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS,
NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS |
NGHTTP2_OPT_DATA_PAD_ALIGNMENT,
&opt_set);
if(rv != 0) {
return -1;
@ -1637,7 +1641,7 @@ void print_usage(std::ostream& out)
{
out << "Usage: nghttp [-Oansuv] [-t <SECONDS>] [-w <WINDOW_BITS>] [-W <WINDOW_BITS>]\n"
<< " [--cert=<CERT>] [--key=<KEY>] [-d <FILE>] [-m <N>]\n"
<< " [-p <PRIORITY>] [-M <N>]\n"
<< " [-p <PRIORITY>] [-M <N>] [-b <ALIGNMENT>]\n"
<< " <URI>..."
<< std::endl;
}
@ -1694,6 +1698,8 @@ void print_help(std::ostream& out)
<< " is large enough as it is seen as unlimited.\n"
<< " -c, --header-table-size=<N>\n"
<< " Specify decoder header table size.\n"
<< " -b, --data-pad=<ALIGNMENT>\n"
<< " Alignment of DATA frame padding.\n"
<< " --color Force colored log output.\n"
<< std::endl;
}
@ -1721,13 +1727,14 @@ int main(int argc, char **argv)
{"pri", required_argument, nullptr, 'p'},
{"peer-max-concurrent-streams", required_argument, nullptr, 'M'},
{"header-table-size", required_argument, nullptr, 'c'},
{"data-pad", required_argument, nullptr, 'b'},
{"cert", required_argument, &flag, 1},
{"key", required_argument, &flag, 2},
{"color", no_argument, &flag, 3},
{nullptr, 0, nullptr, 0 }
};
int option_index = 0;
int c = getopt_long(argc, argv, "M:Oac:d:m:np:hH:vst:uw:W:", long_options,
int c = getopt_long(argc, argv, "M:Oab:c:d:m:np:hH:vst:uw:W:", long_options,
&option_index);
char *end;
if(c == -1) {
@ -1744,6 +1751,9 @@ int main(int argc, char **argv)
case 'h':
print_help(std::cout);
exit(EXIT_SUCCESS);
case 'b':
config.data_pad_alignment = strtol(optarg, nullptr, 10);
break;
case 'n':
config.null_out = true;
break;

View File

@ -75,7 +75,8 @@ int parse_push_config(Config& config, const char *optarg)
namespace {
void print_usage(std::ostream& out)
{
out << "Usage: nghttpd [-DVhv] [-d <PATH>] [--no-tls] <PORT> [<PRIVATE_KEY> <CERT>]"
out << "Usage: nghttpd [-DVhpv] [-d <PATH>] [--no-tls] [-b <ALIGNMENT>]\n"
<< " <PORT> [<PRIVATE_KEY> <CERT>]"
<< std::endl;
}
} // namespace
@ -114,6 +115,8 @@ void print_help(std::ostream& out)
<< " -p/=/foo.png -p/doc=/bar.css\n"
<< " PATH and PUSH_PATHs are relative to document\n"
<< " root. See --htdocs option.\n"
<< " -b, --data-pad=<ALIGNMENT>\n"
<< " Alignment of DATA frame padding.\n"
<< " -h, --help Print this help.\n"
<< std::endl;
}
@ -133,12 +136,13 @@ int main(int argc, char **argv)
{"verify-client", no_argument, nullptr, 'V'},
{"header-table-size", required_argument, nullptr, 'c'},
{"push", required_argument, nullptr, 'p'},
{"data-pad", required_argument, nullptr, 'b'},
{"no-tls", no_argument, &flag, 1},
{"color", no_argument, &flag, 2},
{nullptr, 0, nullptr, 0}
};
int option_index = 0;
int c = getopt_long(argc, argv, "DVc:d:hp:v", long_options, &option_index);
int c = getopt_long(argc, argv, "DVb:c:d:hp:v", long_options, &option_index);
char *end;
if(c == -1) {
break;
@ -150,6 +154,9 @@ int main(int argc, char **argv)
case 'V':
config.verify_client = true;
break;
case 'b':
config.data_pad_alignment = strtol(optarg, nullptr, 10);
break;
case 'd':
config.htdocs = optarg;
break;

View File

@ -193,6 +193,8 @@ int main(int argc, char* argv[])
test_nghttp2_session_set_option) ||
!CU_add_test(pSuite, "session_data_backoff_by_high_pri_frame",
test_nghttp2_session_data_backoff_by_high_pri_frame) ||
!CU_add_test(pSuite, "session_pack_data_with_padding",
test_nghttp2_session_pack_data_with_padding) ||
!CU_add_test(pSuite, "pack_settings_payload",
test_nghttp2_pack_settings_payload) ||
!CU_add_test(pSuite, "frame_pack_headers",

View File

@ -71,6 +71,7 @@ typedef struct {
int header_cb_called;
int begin_headers_cb_called;
nghttp2_nv nv;
size_t data_chunk_len;
} my_user_data;
static void scripted_data_feed_init(scripted_data_feed *df,
@ -187,6 +188,7 @@ static int on_data_chunk_recv_callback(nghttp2_session *session,
{
my_user_data *ud = (my_user_data*)user_data;
++ud->data_chunk_recv_cb_called;
ud->data_chunk_len = len;
return 0;
}
@ -3814,6 +3816,96 @@ void test_nghttp2_session_data_backoff_by_high_pri_frame(void)
nghttp2_session_del(session);
}
static void check_session_recv_data_with_padding(const uint8_t *in,
size_t inlen,
size_t datalen)
{
nghttp2_session *session;
my_user_data ud;
nghttp2_session_callbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.on_frame_recv_callback = on_frame_recv_callback;
callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback;
nghttp2_session_server_new(&session, &callbacks, &ud);
nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
NGHTTP2_PRI_DEFAULT, NGHTTP2_STREAM_OPENING,
NULL);
ud.frame_recv_cb_called = 0;
ud.data_chunk_len = 0;
CU_ASSERT((ssize_t)inlen == nghttp2_session_mem_recv(session, in, inlen));
CU_ASSERT(1 == ud.frame_recv_cb_called);
CU_ASSERT(datalen == ud.data_chunk_len);
nghttp2_session_del(session);
}
void test_nghttp2_session_pack_data_with_padding(void)
{
nghttp2_session *session;
my_user_data ud;
nghttp2_session_callbacks callbacks;
nghttp2_data_provider data_prd;
nghttp2_private_data *frame;
size_t datalen = 55;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.send_callback = block_count_send_callback;
callbacks.on_frame_send_callback = on_frame_send_callback;
data_prd.read_callback = fixed_length_data_source_read_callback;
nghttp2_session_client_new(&session, &callbacks, &ud);
session->data_pad_alignment = 512;
nghttp2_submit_request(session, NGHTTP2_PRI_DEFAULT, NULL, 0, &data_prd,
NULL);
ud.block_count = 1;
ud.data_source_length = datalen;
/* Sends HEADERS */
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type);
frame = OB_DATA(session->aob.item);
CU_ASSERT(session->data_pad_alignment - datalen == frame->padlen);
CU_ASSERT(frame->hd.flags & NGHTTP2_FLAG_PAD_LOW);
CU_ASSERT(frame->hd.flags & NGHTTP2_FLAG_PAD_HIGH);
/* Check reception of this DATA frame */
check_session_recv_data_with_padding
(session->aob.framebuf + session->aob.framebufoff,
session->aob.framebufmark - session->aob.framebufoff,
datalen);
nghttp2_session_del(session);
/* Check without PAD_HIGH */
nghttp2_session_client_new(&session, &callbacks, &ud);
nghttp2_submit_request(session, NGHTTP2_PRI_DEFAULT, NULL, 0, &data_prd,
NULL);
ud.block_count = 1;
ud.data_source_length = datalen;
/* Sends HEADERS */
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type);
frame = OB_DATA(session->aob.item);
CU_ASSERT(session->data_pad_alignment - datalen == frame->padlen);
CU_ASSERT(frame->hd.flags & NGHTTP2_FLAG_PAD_LOW);
CU_ASSERT(0 == (frame->hd.flags & NGHTTP2_FLAG_PAD_HIGH));
/* Check reception of this DATA frame */
check_session_recv_data_with_padding
(session->aob.framebuf + session->aob.framebufoff,
session->aob.framebufmark - session->aob.framebufoff,
datalen);
nghttp2_session_del(session);
}
void test_nghttp2_pack_settings_payload(void)
{
nghttp2_settings_entry iv[2];

View File

@ -87,6 +87,7 @@ void test_nghttp2_session_get_outbound_queue_size(void);
void test_nghttp2_session_get_effective_local_window_size(void);
void test_nghttp2_session_set_option(void);
void test_nghttp2_session_data_backoff_by_high_pri_frame(void);
void test_nghttp2_session_pack_data_with_padding(void);
void test_nghttp2_pack_settings_payload(void);
#endif /* NGHTTP2_SESSION_TEST_H */