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 #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 * @enum
* *
@ -599,6 +607,11 @@ typedef struct {
*/ */
typedef struct { typedef struct {
nghttp2_frame_hd hd; nghttp2_frame_hd hd;
/**
* The length of the padding in this frame. This includes PAD_HIGH
* and PAD_LOW.
*/
size_t padlen;
} nghttp2_data; } nghttp2_data;
/** /**
@ -1309,7 +1322,15 @@ typedef enum {
* will be overwritten if the local endpoint receives * will be overwritten if the local endpoint receives
* SETTINGS_MAX_CONCURRENT_STREAMS from the remote endpoint. * 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; } nghttp2_opt;
/** /**
@ -1330,6 +1351,10 @@ typedef struct {
* :enum:`NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE` * :enum:`NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE`
*/ */
uint8_t 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; } 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) void nghttp2_frame_data_init(nghttp2_data *frame, nghttp2_private_data *pdata)
{ {
frame->hd = pdata->hd; frame->hd = pdata->hd;
frame->padlen = pdata->padlen;
/* flags may have NGHTTP2_FLAG_END_STREAM even if the sent chunk /* flags may have NGHTTP2_FLAG_END_STREAM even if the sent chunk
is not the end of the stream */ is not the end of the stream */
if(!pdata->eof) { 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, void nghttp2_frame_private_data_init(nghttp2_private_data *frame,
uint8_t flags, uint8_t flags,
int32_t stream_id, int32_t stream_id,

View File

@ -71,6 +71,11 @@ typedef struct {
* The data to be sent for this DATA frame. * The data to be sent for this DATA frame.
*/ */
nghttp2_data_provider data_prd; 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 * 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 * |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); 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, void nghttp2_frame_private_data_init(nghttp2_private_data *frame,
uint8_t flags, uint8_t flags,
int32_t stream_id, int32_t stream_id,

View File

@ -223,6 +223,12 @@ static int nghttp2_session_new(nghttp2_session **session_ptr,
(*session_ptr)->opt_flags |= (*session_ptr)->opt_flags |=
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE; 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)->remote_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
(*session_ptr)->recv_window_size = 0; (*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, framebuflen = nghttp2_session_pack_data(session,
&session->aob.framebuf, &session->aob.framebuf,
&session->aob.framebufmax, &session->aob.framebufmax,
&session->aob.framebufoff,
next_readmax, next_readmax,
data_frame); data_frame);
if(framebuflen == NGHTTP2_ERR_DEFERRED) { 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) { } else if(item->frame_cat == NGHTTP2_CAT_DATA) {
nghttp2_private_data *data_frame; nghttp2_private_data *data_frame;
nghttp2_outbound_item* next_item; nghttp2_outbound_item* next_item;
nghttp2_stream *stream;
size_t effective_payloadlen;
data_frame = nghttp2_outbound_item_get_data_frame(session->aob.item); 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) { if(session->callbacks.on_frame_send_callback) {
nghttp2_frame public_data_frame; nghttp2_frame public_data_frame;
nghttp2_frame_data_init(&public_data_frame.data, data_frame); nghttp2_frame_data_init(&public_data_frame.data, data_frame);
@ -1599,17 +1618,15 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session)
return rv; return rv;
} }
} }
if(data_frame->eof && (data_frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
nghttp2_stream *stream = if(stream && data_frame->eof &&
nghttp2_session_get_stream(session, data_frame->hd.stream_id); (data_frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
if(stream) {
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
if(nghttp2_is_fatal(rv)) { if(nghttp2_is_fatal(rv)) {
return rv; return rv;
} }
} }
}
/* If session is closed or RST_STREAM was queued, we won't send /* If session is closed or RST_STREAM was queued, we won't send
further data. */ further data. */
if(data_frame->eof || if(data_frame->eof ||
@ -1618,16 +1635,14 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session)
nghttp2_active_outbound_item_reset(&session->aob); nghttp2_active_outbound_item_reset(&session->aob);
return 0; return 0;
} }
/* Assuming stream is not NULL */
assert(stream);
next_item = nghttp2_session_get_next_ob_item(session); next_item = nghttp2_session_get_next_ob_item(session);
/* If priority of this stream is higher or equal to other stream /* If priority of this stream is higher or equal to other stream
waiting at the top of the queue, we continue to send this waiting at the top of the queue, we continue to send this
data. */ data. */
if(next_item == NULL || session->aob.item->pri < next_item->pri) { if(next_item == NULL || session->aob.item->pri < next_item->pri) {
size_t next_readmax; 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); next_readmax = nghttp2_session_next_data_read(session, stream);
if(next_readmax == 0) { if(next_readmax == 0) {
nghttp2_stream_defer_data(stream, session->aob.item, 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, rv = nghttp2_session_pack_data(session,
&session->aob.framebuf, &session->aob.framebuf,
&session->aob.framebufmax, &session->aob.framebufmax,
&session->aob.framebufoff,
next_readmax, next_readmax,
data_frame); data_frame);
if(nghttp2_is_fatal(rv)) { if(nghttp2_is_fatal(rv)) {
@ -1666,7 +1682,6 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session)
} }
assert(rv >= 0); assert(rv >= 0);
session->aob.framebuflen = session->aob.framebufmark = rv; session->aob.framebuflen = session->aob.framebufmark = rv;
session->aob.framebufoff = 0;
return 0; return 0;
} }
/* Update seq to interleave other streams with the same /* 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) { if(item->frame_cat == NGHTTP2_CAT_CTRL) {
nghttp2_frame *frame = nghttp2_outbound_item_get_ctrl_frame(item); nghttp2_frame *frame = nghttp2_outbound_item_get_ctrl_frame(item);
session->aob.framebufmark = session->aob.framebufmark =
session->aob.framebufoff +
frame->hd.length + NGHTTP2_FRAME_HEAD_LENGTH; frame->hd.length + NGHTTP2_FRAME_HEAD_LENGTH;
r = session_call_before_frame_send(session, frame); r = session_call_before_frame_send(session, frame);
if(nghttp2_is_fatal(r)) { if(nghttp2_is_fatal(r)) {
@ -1748,10 +1764,13 @@ int nghttp2_session_send(nghttp2_session *session)
} else { } else {
nghttp2_private_data *frame; nghttp2_private_data *frame;
frame = nghttp2_outbound_item_get_data_frame(session->aob.item); frame = nghttp2_outbound_item_get_data_frame(session->aob.item);
/* session->aob.framebufmark = session->aob.framebuflen; */
session->aob.framebufmark = session->aob.framebufmark =
session->aob.framebufoff +
frame->hd.length + NGHTTP2_FRAME_HEAD_LENGTH; frame->hd.length + NGHTTP2_FRAME_HEAD_LENGTH;
} }
} }
data = session->aob.framebuf + session->aob.framebufoff; data = session->aob.framebuf + session->aob.framebufoff;
datalen = session->aob.framebufmark - session->aob.framebufoff; datalen = session->aob.framebufmark - session->aob.framebufoff;
sentlen = session->callbacks.send_callback(session, data, datalen, 0, 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; return NGHTTP2_ERR_CALLBACK_FAILURE;
} }
} else { } 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; session->aob.framebufoff += sentlen;
if(session->aob.framebufoff == session->aob.framebufmark) { if(session->aob.framebufoff == session->aob.framebufmark) {
/* Frame has completely sent */ /* Frame has completely sent */
@ -3338,6 +3340,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
switch(iframe->frame.hd.type) { switch(iframe->frame.hd.type) {
case NGHTTP2_DATA: { case NGHTTP2_DATA: {
DEBUGF(fprintf(stderr, "DATA\n")); DEBUGF(fprintf(stderr, "DATA\n"));
iframe->frame.data.padlen = 0;
/* Check stream is open. If it is not open or closing, /* Check stream is open. If it is not open or closing,
ignore payload. */ ignore payload. */
busy = 1; busy = 1;
@ -3351,6 +3354,25 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
if(nghttp2_is_fatal(rv)) { if(nghttp2_is_fatal(rv)) {
return 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; iframe->state = NGHTTP2_IB_READ_DATA;
break; break;
} }
@ -3454,7 +3476,26 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
} }
switch(iframe->frame.hd.type) { switch(iframe->frame.hd.type) {
case NGHTTP2_DATA: 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; break;
case NGHTTP2_HEADERS: case NGHTTP2_HEADERS:
rv = session_process_headers_frame(session); 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", DEBUGF(fprintf(stderr, "readlen=%zu, payloadleft=%zu\n",
readlen, iframe->payloadleft)); readlen, iframe->payloadleft));
if(readlen > 0) { if(readlen > 0) {
size_t data_readlen = readlen;
rv = nghttp2_session_update_recv_connection_window_size rv = nghttp2_session_update_recv_connection_window_size
(session, readlen); (session, readlen);
if(nghttp2_is_fatal(rv)) { 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 rv = session->callbacks.on_data_chunk_recv_callback
(session, (session,
iframe->frame.hd.flags, iframe->frame.hd.flags,
iframe->frame.hd.stream_id, iframe->frame.hd.stream_id,
in - readlen, in - readlen,
readlen, data_readlen,
session->user_data); session->user_data);
if(rv == NGHTTP2_ERR_PAUSE) { if(rv == NGHTTP2_ERR_PAUSE) {
/* Set type to NGHTTP2_DATA, so that we can see what was /* 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, 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 *bufoff_ptr,
size_t datamax, size_t datamax,
nghttp2_private_data *frame) 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; int eof_flags;
uint8_t flags; uint8_t flags;
r = nghttp2_reserve_buffer(buf_ptr, buflen_ptr, framelen); 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; eof_flags = 0;
r = frame->data_prd.read_callback 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); &eof_flags, &frame->data_prd.source, session->user_data);
if(r == NGHTTP2_ERR_DEFERRED || r == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { if(r == NGHTTP2_ERR_DEFERRED || r == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
return r; return r;
@ -3957,19 +4019,56 @@ ssize_t nghttp2_session_pack_data(nghttp2_session *session,
/* This is the error code when callback is failed. */ /* This is the error code when callback is failed. */
return NGHTTP2_ERR_CALLBACK_FAILURE; return NGHTTP2_ERR_CALLBACK_FAILURE;
} }
frame->hd.length = r;
memset(*buf_ptr, 0, NGHTTP2_FRAME_HEAD_LENGTH); /* Clear flags, because this may contain previous flags of previous
nghttp2_put_uint16be(&(*buf_ptr)[0], r); DATA */
frame->hd.flags &= ~(NGHTTP2_FLAG_PAD_HIGH | NGHTTP2_FLAG_PAD_LOW);
flags = 0; 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) { if(eof_flags) {
frame->eof = 1; frame->eof = 1;
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
flags |= NGHTTP2_FLAG_END_STREAM; flags |= NGHTTP2_FLAG_END_STREAM;
} }
} }
(*buf_ptr)[3] = flags; (*buf_ptr)[headoff + 3] = flags;
nghttp2_put_uint32be(&(*buf_ptr)[4], frame->hd.stream_id); nghttp2_put_uint32be(&(*buf_ptr)[headoff + 4], frame->hd.stream_id);
return r+8; *bufoff_ptr = headoff;
return frame->hd.length + NGHTTP2_FRAME_HEAD_LENGTH + headoff;
} }
void* nghttp2_session_get_stream_user_data(nghttp2_session *session, void* nghttp2_session_get_stream_user_data(nghttp2_session *session,

View File

@ -44,7 +44,10 @@
*/ */
typedef enum { typedef enum {
NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE = 1 << 0, 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; } nghttp2_optmask;
typedef struct { typedef struct {
@ -149,6 +152,8 @@ struct nghttp2_session {
size_t num_incoming_streams; size_t num_incoming_streams;
/* The number of bytes allocated for nvbuf */ /* The number of bytes allocated for nvbuf */
size_t nvbuflen; 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). */ /* Next Stream ID. Made unsigned int to detect >= (1 << 31). */
uint32_t next_stream_id; uint32_t next_stream_id;
/* The largest stream ID received so far */ /* 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 * Packs DATA frame |frame| in wire frame format and stores it in
* |*buf_ptr|. The capacity of |*buf_ptr| is |*buflen_ptr| * |*buf_ptr|. The capacity of |*buf_ptr| is |*buflen_ptr|
* length. This function expands |*buf_ptr| as necessary to store * length. This function expands |*buf_ptr| as necessary to store
* given |frame|. It packs header in first 8 bytes. Remaining bytes * given |frame|. It packs header in first 8 bytes starting
* are the DATA apyload and are filled using |frame->data_prd|. The * |*bufoff_ptr| offset. The |*bufoff_ptr| is calculated based on
* length of payload is at most |datamax| bytes. * 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 * This function returns the size of packed frame if it succeeds, or
* one of the following negative error codes: * 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, 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 *bufoff_ptr,
size_t datamax, size_t datamax,
nghttp2_private_data *frame); nghttp2_private_data *frame);

View File

@ -66,6 +66,7 @@ const std::string NGHTTPD_SERVER = "nghttpd nghttp2/" NGHTTP2_VERSION;
Config::Config() Config::Config()
: data_ptr(nullptr), : data_ptr(nullptr),
output_upper_thres(1024*1024), output_upper_thres(1024*1024),
data_pad_alignment(NGHTTP2_DATA_PAD_ALIGNMENT),
header_table_size(-1), header_table_size(-1),
port(0), port(0),
verbose(false), verbose(false),
@ -361,8 +362,14 @@ int Http2Handler::on_connect()
{ {
int r; int r;
nghttp2_session_callbacks callbacks; 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()); 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) { if(r != 0) {
return r; return r;
} }

View File

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

View File

@ -77,6 +77,8 @@ const char* strstatus(nghttp2_error_code error_code)
return "CONNECT_ERROR"; return "CONNECT_ERROR";
case NGHTTP2_ENHANCE_YOUR_CALM: case NGHTTP2_ENHANCE_YOUR_CALM:
return "ENHANCE_YOUR_CALM"; return "ENHANCE_YOUR_CALM";
case NGHTTP2_INADEQUATE_SECURITY:
return "INADEQUATE_SECURITY";
default: default:
return "UNKNOWN"; return "UNKNOWN";
} }
@ -201,11 +203,35 @@ void print_flags(const nghttp2_frame_hd& hd)
if(hd.flags & NGHTTP2_FLAG_END_STREAM) { if(hd.flags & NGHTTP2_FLAG_END_STREAM) {
s += "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; break;
case NGHTTP2_HEADERS: case NGHTTP2_HEADERS:
if(hd.flags & NGHTTP2_FLAG_END_STREAM) { if(hd.flags & NGHTTP2_FLAG_END_STREAM) {
s += "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(hd.flags & NGHTTP2_FLAG_END_HEADERS) {
if(!s.empty()) { if(!s.empty()) {
s += " | "; s += " | ";
@ -265,6 +291,10 @@ void print_frame(print_type ptype, const nghttp2_frame *frame)
} }
switch(frame->hd.type) { switch(frame->hd.type) {
case NGHTTP2_DATA: 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; break;
case NGHTTP2_HEADERS: case NGHTTP2_HEADERS:
if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY) { if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY) {

View File

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

View File

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

View File

@ -193,6 +193,8 @@ int main(int argc, char* argv[])
test_nghttp2_session_set_option) || test_nghttp2_session_set_option) ||
!CU_add_test(pSuite, "session_data_backoff_by_high_pri_frame", !CU_add_test(pSuite, "session_data_backoff_by_high_pri_frame",
test_nghttp2_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", !CU_add_test(pSuite, "pack_settings_payload",
test_nghttp2_pack_settings_payload) || test_nghttp2_pack_settings_payload) ||
!CU_add_test(pSuite, "frame_pack_headers", !CU_add_test(pSuite, "frame_pack_headers",

View File

@ -71,6 +71,7 @@ typedef struct {
int header_cb_called; int header_cb_called;
int begin_headers_cb_called; int begin_headers_cb_called;
nghttp2_nv nv; nghttp2_nv nv;
size_t data_chunk_len;
} my_user_data; } my_user_data;
static void scripted_data_feed_init(scripted_data_feed *df, 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; my_user_data *ud = (my_user_data*)user_data;
++ud->data_chunk_recv_cb_called; ++ud->data_chunk_recv_cb_called;
ud->data_chunk_len = len;
return 0; return 0;
} }
@ -3814,6 +3816,96 @@ void test_nghttp2_session_data_backoff_by_high_pri_frame(void)
nghttp2_session_del(session); 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) void test_nghttp2_pack_settings_payload(void)
{ {
nghttp2_settings_entry iv[2]; 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_get_effective_local_window_size(void);
void test_nghttp2_session_set_option(void); void test_nghttp2_session_set_option(void);
void test_nghttp2_session_data_backoff_by_high_pri_frame(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); void test_nghttp2_pack_settings_payload(void);
#endif /* NGHTTP2_SESSION_TEST_H */ #endif /* NGHTTP2_SESSION_TEST_H */