Add padding to PUSH_PROMISE

This commit is contained in:
Tatsuhiro Tsujikawa 2014-02-15 16:30:43 +09:00
parent 1e95c8b313
commit 3f3f258cd6
5 changed files with 186 additions and 90 deletions

View File

@ -266,7 +266,7 @@ ssize_t nghttp2_frame_pack_headers(uint8_t **buf_ptr,
if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY) { if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY) {
nghttp2_put_uint32be(&(*buf_ptr)[payloadoff], frame->pri); nghttp2_put_uint32be(&(*buf_ptr)[payloadoff], frame->pri);
} }
return frame->hd.length + NGHTTP2_FRAME_HEAD_LENGTH + *bufoff_ptr; return *bufoff_ptr + NGHTTP2_FRAME_HEAD_LENGTH + frame->hd.length;
} }
int nghttp2_frame_unpack_headers_payload(nghttp2_headers *frame, int nghttp2_frame_unpack_headers_payload(nghttp2_headers *frame,
@ -396,35 +396,45 @@ int nghttp2_frame_unpack_settings_payload2(nghttp2_settings_entry **iv_ptr,
ssize_t nghttp2_frame_pack_push_promise(uint8_t **buf_ptr, ssize_t nghttp2_frame_pack_push_promise(uint8_t **buf_ptr,
size_t *buflen_ptr, size_t *buflen_ptr,
size_t *bufoff_ptr,
nghttp2_push_promise *frame, nghttp2_push_promise *frame,
nghttp2_hd_deflater *deflater) nghttp2_hd_deflater *deflater)
{ {
ssize_t framelen; size_t payloadoff = NGHTTP2_FRAME_HEAD_LENGTH + 2;
size_t nv_offset = NGHTTP2_FRAME_HEAD_LENGTH + 4; size_t nv_offset = payloadoff + 4;
ssize_t rv; ssize_t rv;
size_t payloadlen;
rv = nghttp2_hd_deflate_hd(deflater, buf_ptr, buflen_ptr, nv_offset, rv = nghttp2_hd_deflate_hd(deflater, buf_ptr, buflen_ptr, nv_offset,
frame->nva, frame->nvlen); frame->nva, frame->nvlen);
if(rv < 0) { if(rv < 0) {
return rv; return rv;
} }
framelen = rv + nv_offset;
if(NGHTTP2_FRAME_HEAD_LENGTH + NGHTTP2_MAX_FRAME_LENGTH < rv + nv_offset) { payloadlen = 4 + rv;
frame->hd.length = NGHTTP2_MAX_FRAME_LENGTH;
frame->hd.flags &= ~NGHTTP2_FLAG_END_HEADERS; *bufoff_ptr = 2;
} else { frame->padlen = 0;
frame->hd.length = framelen - NGHTTP2_FRAME_HEAD_LENGTH; frame->hd.length = payloadlen;
}
/* If frame->nvlen == 0, *buflen_ptr may be smaller than /* If frame->nvlen == 0, *buflen_ptr may be smaller than
nv_offset */ nv_offset */
rv = nghttp2_reserve_buffer(buf_ptr, buflen_ptr, nv_offset); rv = nghttp2_reserve_buffer(buf_ptr, buflen_ptr, nv_offset);
if(rv < 0) { if(rv < 0) {
return rv; return rv;
} }
memset(*buf_ptr, 0, nv_offset); memset(*buf_ptr + *bufoff_ptr, 0, NGHTTP2_FRAME_HEAD_LENGTH);
/* pack ctrl header after length is determined */ /* pack ctrl header after length is determined */
nghttp2_frame_pack_frame_hd(*buf_ptr, &frame->hd); if(NGHTTP2_MAX_FRAME_LENGTH < payloadlen) {
nghttp2_put_uint32be(&(*buf_ptr)[8], frame->promised_stream_id); /* Needs CONTINUATION */
return framelen; nghttp2_frame_hd hd = frame->hd;
hd.flags &= ~NGHTTP2_FLAG_END_HEADERS;
hd.length = NGHTTP2_MAX_FRAME_LENGTH;
nghttp2_frame_pack_frame_hd(*buf_ptr + *bufoff_ptr, &hd);
} else {
nghttp2_frame_pack_frame_hd(*buf_ptr + *bufoff_ptr, &frame->hd);
}
nghttp2_put_uint32be(&(*buf_ptr)[payloadoff], frame->promised_stream_id);
return *bufoff_ptr + NGHTTP2_FRAME_HEAD_LENGTH + frame->hd.length;
} }
int nghttp2_frame_unpack_push_promise_payload(nghttp2_push_promise *frame, int nghttp2_frame_unpack_push_promise_payload(nghttp2_push_promise *frame,

View File

@ -256,12 +256,20 @@ int nghttp2_frame_unpack_settings_payload2(nghttp2_settings_entry **iv_ptr,
* expansion occurred, memory previously pointed by |*buf_ptr| may * expansion occurred, memory previously pointed by |*buf_ptr| may
* change. |*buf_ptr| and |*buflen_ptr| are updated accordingly. * change. |*buf_ptr| and |*buflen_ptr| are updated accordingly.
* *
* The first byte the frame is serialized is returned in the
* |*bufoff_ptr|. Currently, it is always 2 to account for possible
* PAD_HIGH and PAD_LOW.
*
* frame->hd.length is assigned after length is determined during * frame->hd.length is assigned after length is determined during
* packing process. If payload length is strictly larger than * packing process. If payload length is strictly larger than
* NGHTTP2_MAX_FRAME_LENGTH, payload data is still serialized as is, * NGHTTP2_MAX_FRAME_LENGTH, payload data is still serialized as is,
* but frame->hd.length is set to NGHTTP2_MAX_FRAME_LENGTH and * but frame->hd.length is set to NGHTTP2_MAX_FRAME_LENGTH and
* NGHTTP2_FLAG_END_HEADERS flag is cleared from frame->hd.flags. * NGHTTP2_FLAG_END_HEADERS flag is cleared from frame->hd.flags.
* *
* This function returns the size of packed frame (which includes
* |*bufoff_ptr| bytes) if it succeeds, or returns one of the
* following negative error codes:
*
* This function returns the size of packed frame if it succeeds, or * This function returns the size of packed frame if it succeeds, or
* returns one of the following negative error codes: * returns one of the following negative error codes:
* *
@ -274,6 +282,7 @@ int nghttp2_frame_unpack_settings_payload2(nghttp2_settings_entry **iv_ptr,
*/ */
ssize_t nghttp2_frame_pack_push_promise(uint8_t **buf_ptr, ssize_t nghttp2_frame_pack_push_promise(uint8_t **buf_ptr,
size_t *buflen_ptr, size_t *buflen_ptr,
size_t *bufoff_ptr,
nghttp2_push_promise *frame, nghttp2_push_promise *frame,
nghttp2_hd_deflater *deflater); nghttp2_hd_deflater *deflater);

View File

@ -1095,6 +1095,76 @@ static ssize_t session_call_select_padding(nghttp2_session *session,
return frame->hd.length; return frame->hd.length;
} }
/* Add padding to HEADERS or PUSH_PROMISE. We use
frame->headers.padlen in this function to use the fact that
frame->push_promise has also padlen in the same position. */
static ssize_t session_headers_add_pad(nghttp2_session *session,
nghttp2_frame *frame)
{
int rv;
ssize_t padded_payloadlen;
padded_payloadlen = session_call_select_padding(session, frame,
frame->hd.length + 1024);
if(nghttp2_is_fatal(padded_payloadlen)) {
return padded_payloadlen;
}
frame->headers.padlen = padded_payloadlen - frame->hd.length;
frame->hd.length = padded_payloadlen;
DEBUGF(fprintf(stderr, "payloadlen=%zu, padlen=%zu\n",
frame->hd.length, frame->headers.padlen));
if(frame->hd.length > NGHTTP2_MAX_FRAME_LENGTH) {
nghttp2_frame_hd hd = frame->hd;
hd.flags &= ~NGHTTP2_FLAG_END_HEADERS;
hd.length = NGHTTP2_MAX_FRAME_LENGTH;
if(NGHTTP2_MAX_FRAME_LENGTH >
frame->hd.length - frame->headers.padlen) {
size_t padlen = NGHTTP2_MAX_FRAME_LENGTH -
(frame->hd.length - frame->headers.padlen);
DEBUGF(fprintf(stderr, "padding across 2 frames\n"));
DEBUGF(fprintf(stderr, "first HEADERS/PUSH_PROMISE "
"payloadlen=%zu, padlen=%zu\n", hd.length, padlen));
rv = nghttp2_frame_add_pad(&session->aob.framebuf,
&session->aob.framebufmax,
&session->aob.framebufoff,
&hd.flags,
hd.length - padlen,
padlen);
if(nghttp2_is_fatal(rv)) {
return rv;
}
} else {
/* PAD_HIGH and PAD_LOW will be added in
nghttp2_session_after_frame_sent(). */
}
nghttp2_frame_pack_frame_hd
(session->aob.framebuf + session->aob.framebufoff, &hd);
/* At this point, framebuflen > session->aob.framebufmax. But
before we access the missing part, we will allocate it in
nghttp2_session_after_frame_sent(). */
} else if(frame->headers.padlen > 0) {
rv = nghttp2_frame_add_pad(&session->aob.framebuf,
&session->aob.framebufmax,
&session->aob.framebufoff,
&frame->hd.flags,
frame->hd.length - frame->headers.padlen,
frame->headers.padlen);
if(nghttp2_is_fatal(rv)) {
return rv;
}
nghttp2_frame_pack_frame_hd
(session->aob.framebuf + session->aob.framebufoff, &frame->hd);
}
return session->aob.framebufoff + NGHTTP2_FRAME_HEAD_LENGTH
+ frame->hd.length;
}
static ssize_t nghttp2_session_prep_frame(nghttp2_session *session, static ssize_t nghttp2_session_prep_frame(nghttp2_session *session,
nghttp2_outbound_item *item) nghttp2_outbound_item *item)
{ {
@ -1106,7 +1176,6 @@ static ssize_t nghttp2_session_prep_frame(nghttp2_session *session,
case NGHTTP2_HEADERS: { case NGHTTP2_HEADERS: {
int r; int r;
nghttp2_headers_aux_data *aux_data; nghttp2_headers_aux_data *aux_data;
ssize_t padded_payloadlen;
aux_data = (nghttp2_headers_aux_data*)item->aux_data; aux_data = (nghttp2_headers_aux_data*)item->aux_data;
if(frame->hd.stream_id == -1) { if(frame->hd.stream_id == -1) {
/* initial HEADERS, which opens stream */ /* initial HEADERS, which opens stream */
@ -1140,71 +1209,9 @@ static ssize_t nghttp2_session_prep_frame(nghttp2_session *session,
if(framebuflen < 0) { if(framebuflen < 0) {
return framebuflen; return framebuflen;
} }
padded_payloadlen = session_call_select_padding framebuflen = session_headers_add_pad(session, frame);
(session, frame, frame->hd.length + 1024); if(framebuflen < 0) {
if(nghttp2_is_fatal(padded_payloadlen)) { return framebuflen;
return padded_payloadlen;
}
frame->headers.padlen = padded_payloadlen - frame->hd.length;
frame->hd.length = padded_payloadlen;
DEBUGF(fprintf(stderr, "payloadlen=%zu, padlen=%zu\n",
frame->hd.length, frame->headers.padlen));
if(frame->hd.length > NGHTTP2_MAX_FRAME_LENGTH) {
if(NGHTTP2_MAX_FRAME_LENGTH >
frame->hd.length - frame->headers.padlen) {
size_t padlen = NGHTTP2_MAX_FRAME_LENGTH -
(frame->hd.length - frame->headers.padlen);
nghttp2_frame_hd hd = frame->hd;
DEBUGF(fprintf(stderr, "padding across 2 frames\n"));
hd.flags &= ~NGHTTP2_FLAG_END_HEADERS;
hd.length = NGHTTP2_MAX_FRAME_LENGTH;
DEBUGF(fprintf(stderr, "first HEADERS payloadlen=%zu, padlen=%zu\n",
hd.length, padlen));
r = nghttp2_frame_add_pad(&session->aob.framebuf,
&session->aob.framebufmax,
&session->aob.framebufoff,
&hd.flags,
hd.length - padlen,
padlen);
if(nghttp2_is_fatal(r)) {
return r;
}
framebuflen = session->aob.framebufoff + frame->hd.length
+ NGHTTP2_FRAME_HEAD_LENGTH;
nghttp2_frame_pack_frame_hd
(session->aob.framebuf + session->aob.framebufoff, &hd);
} else {
/* PAD_HIGH and PAD_LOW will be added in
nghttp2_session_after_frame_sent(). */
framebuflen += frame->headers.padlen;
}
/* At this point, framebuflen > session->aob.framebufmax. But
before we access the missing part, we will allocate it in
nghttp2_session_after_frame_sent(). */
} else if(frame->hd.length <= NGHTTP2_MAX_FRAME_LENGTH &&
frame->headers.padlen > 0) {
r = nghttp2_frame_add_pad(&session->aob.framebuf,
&session->aob.framebufmax,
&session->aob.framebufoff,
&frame->hd.flags,
frame->hd.length - frame->headers.padlen,
frame->headers.padlen);
if(nghttp2_is_fatal(r)) {
return r;
}
framebuflen = session->aob.framebufoff + frame->hd.length
+ NGHTTP2_FRAME_HEAD_LENGTH;
nghttp2_frame_pack_frame_hd
(session->aob.framebuf + session->aob.framebufoff, &frame->hd);
} }
switch(frame->headers.cat) { switch(frame->headers.cat) {
@ -1281,11 +1288,17 @@ static ssize_t nghttp2_session_prep_frame(nghttp2_session *session,
session->next_stream_id += 2; session->next_stream_id += 2;
framebuflen = nghttp2_frame_pack_push_promise(&session->aob.framebuf, framebuflen = nghttp2_frame_pack_push_promise(&session->aob.framebuf,
&session->aob.framebufmax, &session->aob.framebufmax,
&session->aob.framebufoff,
&frame->push_promise, &frame->push_promise,
&session->hd_deflater); &session->hd_deflater);
if(framebuflen < 0) { if(framebuflen < 0) {
return framebuflen; return framebuflen;
} }
framebuflen = session_headers_add_pad(session, frame);
if(framebuflen < 0) {
return framebuflen;
}
stream = nghttp2_session_get_stream(session, frame->hd.stream_id); stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
assert(stream); assert(stream);
if(nghttp2_session_open_stream if(nghttp2_session_open_stream
@ -1538,6 +1551,9 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session)
if(cont_hd.length < frame->headers.padlen) { if(cont_hd.length < frame->headers.padlen) {
padlen = cont_hd.length; padlen = cont_hd.length;
} else { } else {
/* We use frame->headers.padlen for PUSH_PROMISE too. This
is possible because padlen is located in the same
position. */
padlen = frame->headers.padlen; padlen = frame->headers.padlen;
} }
DEBUGF(fprintf(stderr, DEBUGF(fprintf(stderr,
@ -3454,10 +3470,16 @@ static int inbound_frame_handle_pad(nghttp2_inbound_frame *iframe,
if((hd->flags & NGHTTP2_FLAG_PAD_LOW) == 0) { if((hd->flags & NGHTTP2_FLAG_PAD_LOW) == 0) {
return -1; return -1;
} }
if(hd->length < 2) {
return -1;
}
inbound_frame_reset_left(iframe, 2); inbound_frame_reset_left(iframe, 2);
return 1; return 1;
} }
if(hd->flags & NGHTTP2_FLAG_PAD_LOW) { if(hd->flags & NGHTTP2_FLAG_PAD_LOW) {
if(hd->length < 1) {
return -1;
}
inbound_frame_reset_left(iframe, 1); inbound_frame_reset_left(iframe, 1);
return 1; return 1;
} }
@ -3657,6 +3679,21 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
break; break;
case NGHTTP2_PUSH_PROMISE: case NGHTTP2_PUSH_PROMISE:
DEBUGF(fprintf(stderr, "PUSH_PROMISE\n")); DEBUGF(fprintf(stderr, "PUSH_PROMISE\n"));
rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd);
if(rv < 0) {
busy = 1;
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
rv = nghttp2_session_terminate_session(session,
NGHTTP2_PROTOCOL_ERROR);
if(nghttp2_is_fatal(rv)) {
return rv;
}
break;
}
if(rv == 1) {
iframe->state = NGHTTP2_IB_READ_NBYTE;
break;
}
if(iframe->payloadleft < 4) { if(iframe->payloadleft < 4) {
busy = 1; busy = 1;
iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
@ -3762,6 +3799,29 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
nghttp2_inbound_frame_reset(session); nghttp2_inbound_frame_reset(session);
break; break;
case NGHTTP2_PUSH_PROMISE: case NGHTTP2_PUSH_PROMISE:
if(iframe->padlen == 0 &&
iframe->frame.hd.flags & NGHTTP2_FLAG_PAD_LOW) {
rv = inbound_frame_compute_pad(iframe);
if(rv < 0) {
busy = 1;
rv = nghttp2_session_terminate_session(session,
NGHTTP2_PROTOCOL_ERROR);
if(nghttp2_is_fatal(rv)) {
return rv;
}
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
break;
}
iframe->frame.push_promise.padlen = rv;
if(iframe->payloadleft < 4) {
busy = 1;
iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
break;
}
iframe->state = NGHTTP2_IB_READ_NBYTE;
inbound_frame_reset_left(iframe, 4);
break;
}
rv = session_process_push_promise_frame(session); rv = session_process_push_promise_frame(session);
if(nghttp2_is_fatal(rv)) { if(nghttp2_is_fatal(rv)) {
return rv; return rv;

View File

@ -263,10 +263,12 @@ void test_nghttp2_frame_pack_push_promise()
nghttp2_push_promise frame, oframe; nghttp2_push_promise frame, oframe;
uint8_t *buf = NULL; uint8_t *buf = NULL;
size_t buflen = 0; size_t buflen = 0;
size_t bufoff;
ssize_t framelen; ssize_t framelen;
nghttp2_nv *nva; nghttp2_nv *nva;
ssize_t nvlen; ssize_t nvlen;
nva_out out; nva_out out;
ssize_t nv_offset;
nva_out_init(&out); nva_out_init(&out);
nghttp2_hd_deflate_init(&deflater); nghttp2_hd_deflate_init(&deflater);
@ -276,16 +278,19 @@ void test_nghttp2_frame_pack_push_promise()
nvlen = HEADERS_LENGTH; nvlen = HEADERS_LENGTH;
nghttp2_frame_push_promise_init(&frame, NGHTTP2_FLAG_END_PUSH_PROMISE, nghttp2_frame_push_promise_init(&frame, NGHTTP2_FLAG_END_PUSH_PROMISE,
1000000007, (1U << 31) - 1, nva, nvlen); 1000000007, (1U << 31) - 1, nva, nvlen);
framelen = nghttp2_frame_pack_push_promise(&buf, &buflen, &frame, &deflater); framelen = nghttp2_frame_pack_push_promise(&buf, &buflen, &bufoff, &frame,
&deflater);
CU_ASSERT(0 == unpack_frame((nghttp2_frame*)&oframe, buf, framelen)); CU_ASSERT(0 == unpack_frame((nghttp2_frame*)&oframe,
check_frame_header(framelen - NGHTTP2_FRAME_HEAD_LENGTH, buf + bufoff, framelen - bufoff));
check_frame_header(framelen - bufoff - NGHTTP2_FRAME_HEAD_LENGTH,
NGHTTP2_PUSH_PROMISE, NGHTTP2_PUSH_PROMISE,
NGHTTP2_FLAG_END_PUSH_PROMISE, 1000000007, &oframe.hd); NGHTTP2_FLAG_END_PUSH_PROMISE, 1000000007, &oframe.hd);
CU_ASSERT((1U << 31) - 1 == oframe.promised_stream_id); CU_ASSERT((1U << 31) - 1 == oframe.promised_stream_id);
CU_ASSERT(framelen - 12 == nv_offset = bufoff + NGHTTP2_FRAME_HEAD_LENGTH + 4;
inflate_hd(&inflater, &out, buf + 12, framelen - 12)); CU_ASSERT(framelen - nv_offset ==
inflate_hd(&inflater, &out, buf + nv_offset, framelen - nv_offset));
CU_ASSERT(7 == out.nvlen); CU_ASSERT(7 == out.nvlen);
CU_ASSERT(nvnameeq("method", &out.nva[0])); CU_ASSERT(nvnameeq("method", &out.nva[0]));

View File

@ -4044,7 +4044,7 @@ void test_nghttp2_session_pack_headers_with_padding(void)
nghttp2_session *session, *sv_session; nghttp2_session *session, *sv_session;
accumulator acc; accumulator acc;
my_user_data ud; my_user_data ud;
nghttp2_session_callbacks callbacks, sv_callbacks; nghttp2_session_callbacks callbacks;
nghttp2_nv nva[8190]; nghttp2_nv nva[8190];
size_t i; size_t i;
@ -4059,15 +4059,13 @@ void test_nghttp2_session_pack_headers_with_padding(void)
callbacks.send_callback = accumulator_send_callback; callbacks.send_callback = accumulator_send_callback;
callbacks.on_frame_send_callback = on_frame_send_callback; callbacks.on_frame_send_callback = on_frame_send_callback;
callbacks.select_padding_callback = select_padding_callback; callbacks.select_padding_callback = select_padding_callback;
callbacks.on_frame_recv_callback = on_frame_recv_callback;
acc.length = 0; acc.length = 0;
ud.acc = &acc; ud.acc = &acc;
memset(&sv_callbacks, 0, sizeof(sv_callbacks));
sv_callbacks.on_frame_recv_callback = on_frame_recv_callback;
nghttp2_session_client_new(&session, &callbacks, &ud); nghttp2_session_client_new(&session, &callbacks, &ud);
nghttp2_session_server_new(&sv_session, &sv_callbacks, &ud); nghttp2_session_server_new(&sv_session, &callbacks, &ud);
ud.padding_boundary = 16385; ud.padding_boundary = 16385;
@ -4083,6 +4081,20 @@ void test_nghttp2_session_pack_headers_with_padding(void)
CU_ASSERT(1 == ud.frame_recv_cb_called); CU_ASSERT(1 == ud.frame_recv_cb_called);
CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(sv_session)); CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(sv_session));
/* Check PUSH_PROMISE */
CU_ASSERT(0 ==
nghttp2_submit_push_promise(sv_session, NGHTTP2_FLAG_NONE, 1,
nva, ARRLEN(nva)));
acc.length = 0;
CU_ASSERT(0 == nghttp2_session_send(sv_session));
CU_ASSERT(acc.length > NGHTTP2_MAX_FRAME_LENGTH);
ud.frame_recv_cb_called = 0;
CU_ASSERT((ssize_t)acc.length ==
nghttp2_session_mem_recv(session, acc.buf, acc.length));
CU_ASSERT(1 == ud.frame_recv_cb_called);
CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
nghttp2_session_del(sv_session); nghttp2_session_del(sv_session);
nghttp2_session_del(session); nghttp2_session_del(session);
} }