diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 773b0e2b..9864cdb8 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -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; /** diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index 1b9a4c6b..b0b29b47 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -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, diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h index 841bce35..79e4b089 100644 --- a/lib/nghttp2_frame.h +++ b/lib/nghttp2_frame.h @@ -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, diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 7020947a..7068e32a 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -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, diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index f4f17922..9e4fb484 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -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); diff --git a/src/HttpServer.cc b/src/HttpServer.cc index cd3b1999..6e7374e0 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -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; } diff --git a/src/HttpServer.h b/src/HttpServer.h index 3d909263..aeb6f43d 100644 --- a/src/HttpServer.h +++ b/src/HttpServer.h @@ -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; diff --git a/src/app_helper.cc b/src/app_helper.cc index ce565202..46a231ba 100644 --- a/src/app_helper.cc +++ b/src/app_helper.cc @@ -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) { diff --git a/src/nghttp.cc b/src/nghttp.cc index 04caa96d..4d11a09e 100644 --- a/src/nghttp.cc +++ b/src/nghttp.cc @@ -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 ] [-w ] [-W ]\n" << " [--cert=] [--key=] [-d ] [-m ]\n" - << " [-p ] [-M ]\n" + << " [-p ] [-M ] [-b ]\n" << " ..." << 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" << " Specify decoder header table size.\n" + << " -b, --data-pad=\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; diff --git a/src/nghttpd.cc b/src/nghttpd.cc index 582c457c..87db6143 100644 --- a/src/nghttpd.cc +++ b/src/nghttpd.cc @@ -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 ] [--no-tls] [ ]" + out << "Usage: nghttpd [-DVhpv] [-d ] [--no-tls] [-b ]\n" + << " [ ]" << 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=\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; diff --git a/tests/main.c b/tests/main.c index f5df28cf..063c881e 100644 --- a/tests/main.c +++ b/tests/main.c @@ -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", diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index e42529f7..98df0836 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -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]; diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index 6b4977f8..4d88103b 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -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 */