Commit 84cbebf4 authored by Tatsuhiro Tsujikawa's avatar Tatsuhiro Tsujikawa

Implement server push

parent 321136b0
......@@ -575,6 +575,14 @@ typedef struct {
* The promised stream ID
*/
int32_t promised_stream_id;
/**
* The name/value pairs.
*/
nghttp2_nv *nva;
/**
* The number of name/value pairs in |nva|.
*/
size_t nvlen;
} nghttp2_push_promise;
/**
......@@ -1405,11 +1413,10 @@ int nghttp2_submit_response(nghttp2_session *session,
/**
* @function
*
* Submits SYN_STREAM frame. The |flags| is bitwise OR of the
* Submits HEADERS frame. The |flags| is bitwise OR of the
* following values:
*
* * :enum:`NGHTTP2_FLAG_END_STREAM`
* * :enum:`NGHTTP2_FLAG_END_HEADERS`
* * :enum:`NGHTTP2_FLAG_PRIORITY`
*
* If |flags| includes :enum:`NGHTTP2_FLAG_END_STREAM`, this frame has
......@@ -1431,8 +1438,10 @@ int nghttp2_submit_response(nghttp2_session *session,
* This function creates copies of all name/value pairs in |nv|. It
* also lower-cases all names in |nv|.
*
* The |stream_user_data| is a pointer to an arbitrary
* data which is associated to the stream this frame will open.
* The |stream_user_data| is a pointer to an arbitrary data which is
* associated to the stream this frame will open. Therefore it is only
* used if this frame opens streams, in other words, it changes stream
* state from idle or reserved to open.
*
* This function is low-level in a sense that the application code can
* specify flags and the Associated-To-Stream-ID directly. For usual
......@@ -1527,6 +1536,34 @@ int nghttp2_submit_rst_stream(nghttp2_session *session, int32_t stream_id,
int nghttp2_submit_settings(nghttp2_session *session,
const nghttp2_settings_entry *iv, size_t niv);
/**
* @function
*
* Submits PUSH_PROMISE frame. The |flags| is currently ignored.
*
* The |stream_id| must be client initiated stream ID.
*
* The |nv| contains the name/value pairs. For i >= 0, ``nv[2*i]``
* contains a pointer to the name string and ``nv[2*i+1]`` contains a
* pointer to the value string. The one beyond last value must be
* ``NULL``. That is, if the |nv| contains N name/value pairs,
* ``nv[2*N]`` must be ``NULL``.
*
* This function creates copies of all name/value pairs in |nv|. It
* also lower-cases all names in |nv|.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
* The |nv| includes empty name or NULL value.
* :enum:`NGHTTP2_ERR_NOMEM`
* Out of memory.
*/
int nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags,
int32_t stream_id, const char **nv);
/**
* @function
*
......
......@@ -252,6 +252,22 @@ void nghttp2_frame_settings_free(nghttp2_settings *frame)
free(frame->iv);
}
void nghttp2_frame_push_promise_init(nghttp2_push_promise *frame,
uint8_t flags, int32_t stream_id,
int32_t promised_stream_id,
nghttp2_nv *nva, size_t nvlen)
{
memset(frame, 0, sizeof(nghttp2_push_promise));
nghttp2_frame_set_hd(&frame->hd, 0, NGHTTP2_PUSH_PROMISE, flags, stream_id);
frame->promised_stream_id = promised_stream_id;
frame->nva = nva;
frame->nvlen = nvlen;
}
void nghttp2_frame_push_promise_free(nghttp2_push_promise *frame)
{
nghttp2_nv_array_del(frame->nva);
}
void nghttp2_frame_ping_init(nghttp2_ping *frame, uint8_t flags,
const uint8_t *opaque_data)
......@@ -490,6 +506,72 @@ int nghttp2_frame_unpack_settings(nghttp2_settings *frame,
return 0;
}
ssize_t nghttp2_frame_pack_push_promise(uint8_t **buf_ptr,
size_t *buflen_ptr,
nghttp2_push_promise *frame,
nghttp2_hd_context *deflater)
{
ssize_t framelen;
size_t nv_offset = NGHTTP2_FRAME_HEAD_LENGTH + 4;
ssize_t rv;
rv = nghttp2_hd_deflate_hd(deflater, buf_ptr, buflen_ptr, nv_offset,
frame->nva, frame->nvlen);
if(rv < 0) {
return rv;
}
framelen = rv + nv_offset;
frame->hd.length = framelen - NGHTTP2_FRAME_HEAD_LENGTH;
/* If frame->nvlen == 0, *buflen_ptr may be smaller than
nv_offset */
rv = nghttp2_reserve_buffer(buf_ptr, buflen_ptr, nv_offset);
if(rv < 0) {
return rv;
}
memset(*buf_ptr, 0, nv_offset);
/* pack ctrl header after length is determined */
nghttp2_frame_pack_frame_hd(*buf_ptr, &frame->hd);
nghttp2_put_uint32be(&(*buf_ptr)[8], frame->promised_stream_id);
return framelen;
}
int nghttp2_frame_unpack_push_promise(nghttp2_push_promise *frame,
const uint8_t *head, size_t headlen,
const uint8_t *payload,
size_t payloadlen,
nghttp2_hd_context *inflater)
{
ssize_t r;
r = nghttp2_frame_unpack_push_promise_without_nv(frame, head, headlen,
payload, payloadlen);
if(r < 0) {
return r;
}
r = nghttp2_hd_inflate_hd(inflater, &frame->nva,
(uint8_t*)payload + 4, payloadlen - 4);
if(r < 0) {
return r;
}
frame->nvlen = r;
return 0;
}
int nghttp2_frame_unpack_push_promise_without_nv(nghttp2_push_promise *frame,
const uint8_t *head,
size_t headlen,
const uint8_t *payload,
size_t payloadlen)
{
nghttp2_frame_unpack_frame_hd(&frame->hd, head);
if(payloadlen < 4) {
return NGHTTP2_ERR_INVALID_FRAME;
}
frame->promised_stream_id = nghttp2_get_uint32(payload) &
NGHTTP2_STREAM_ID_MASK;
frame->nva = NULL;
frame->nvlen = 0;
return 0;
}
ssize_t nghttp2_frame_pack_ping(uint8_t **buf_ptr, size_t *buflen_ptr,
nghttp2_ping *frame)
{
......
......@@ -238,6 +238,78 @@ int nghttp2_frame_unpack_settings(nghttp2_settings *frame,
const uint8_t *head, size_t headlen,
const uint8_t *payload, size_t payloadlen);
/*
* Packs PUSH_PROMISE frame |frame| in wire format and store it in
* |*buf_ptr|. The capacity of |*buf_ptr| is |*buflen_ptr| bytes.
* This function expands |*buf_ptr| as necessary to store frame. When
* expansion occurred, memory previously pointed by |*buf_ptr| may be
* freed. |*buf_ptr| and |*buflen_ptr| are updated accordingly.
*
* frame->hd.length is assigned after length is determined during
* packing process.
*
* This function returns the size of packed frame if it succeeds, or
* returns one of the following negative error codes:
*
* NGHTTP2_ERR_HEADER_COMP
* The deflate operation failed.
* NGHTTP2_ERR_FRAME_TOO_LARGE
* The length of the frame is too large.
* NGHTTP2_ERR_NOMEM
* Out of memory.
*/
ssize_t nghttp2_frame_pack_push_promise(uint8_t **buf_ptr,
size_t *buflen_ptr,
nghttp2_push_promise *frame,
nghttp2_hd_context *deflater);
/*
* Unpacks PUSH_PROMISE frame byte sequence into |frame|. The control
* frame header is given in |head| with |headlen| length. In the spec,
* headlen is 8 bytes. |payload| is the data after frame header and
* just before name/value header block.
*
* The |inflater| inflates name/value header block.
*
* This function also validates the name/value pairs. If unpacking
* succeeds but validation fails, it is indicated by returning
* NGHTTP2_ERR_INVALID_HEADER_BLOCK.
*
* This function returns 0 if it succeeds or one of the following
* negative error codes:
*
* NGHTTP2_ERR_HEADER_COMP
* The inflate operation failed.
* NGHTTP2_ERR_INVALID_HEADER_BLOCK
* Unpacking succeeds but the header block is invalid.
* NGHTTP2_ERR_INVALID_FRAME
* The input data are invalid.
* NGHTTP2_ERR_NOMEM
* Out of memory.
*/
int nghttp2_frame_unpack_push_promise(nghttp2_push_promise *frame,
const uint8_t *head, size_t headlen,
const uint8_t *payload,
size_t payloadlen,
nghttp2_hd_context *inflater);
/*
* Unpacks PUSH_PROMISE frame byte sequence into |frame|. This function
* only unapcks bytes that come before name/value header block.
*
* This function returns 0 if it succeeds or one of the following
* negative error codes:
*
* NGHTTP2_ERR_INVALID_FRAME
* The input data are invalid.
*/
int nghttp2_frame_unpack_push_promise_without_nv(nghttp2_push_promise *frame,
const uint8_t *head,
size_t headlen,
const uint8_t *payload,
size_t payloadlen);
/*
* Packs PING frame |frame| in wire format and store it in
* |*buf_ptr|. The capacity of |*buf_ptr| is |*buflen_ptr|
......@@ -367,6 +439,17 @@ void nghttp2_frame_rst_stream_init(nghttp2_rst_stream *frame,
void nghttp2_frame_rst_stream_free(nghttp2_rst_stream *frame);
/*
* Initializes PUSH_PROMISE frame |frame| with given values. |frame|
* takes ownership of |nva|, so caller must not free it.
*/
void nghttp2_frame_push_promise_init(nghttp2_push_promise *frame,
uint8_t flags, int32_t stream_id,
int32_t promised_stream_id,
nghttp2_nv *nva, size_t nvlen);
void nghttp2_frame_push_promise_free(nghttp2_push_promise *frame);
/*
* Initializes SETTINGS frame |frame| with given values. |frame| takes
* ownership of |iv|, so caller must not free it. The |flags| are
......
......@@ -50,6 +50,9 @@ void nghttp2_outbound_item_free(nghttp2_outbound_item *item)
case NGHTTP2_SETTINGS:
nghttp2_frame_settings_free(&frame->settings);
break;
case NGHTTP2_PUSH_PROMISE:
nghttp2_frame_push_promise_free(&frame->push_promise);
break;
case NGHTTP2_PING:
nghttp2_frame_ping_free(&frame->ping);
break;
......
This diff is collapsed.
......@@ -345,6 +345,10 @@ int nghttp2_session_on_syn_reply_received(nghttp2_session *session,
nghttp2_frame *frame,
nghttp2_stream *stream);
int nghttp2_session_on_push_reply_received(nghttp2_session *session,
nghttp2_frame *frame,
nghttp2_stream *stream);
/*
* Called when HEADERS is received, assuming |frame| is properly
* initialized. This function does first validate received frame and
......@@ -392,6 +396,19 @@ int nghttp2_session_on_rst_stream_received(nghttp2_session *session,
int nghttp2_session_on_settings_received(nghttp2_session *session,
nghttp2_frame *frame);
/*
* Called when PUSH_PROMISE is received, assuming |frame| is properly
* initialized.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* NGHTTP2_ERR_NOMEM
* Out of memory.
*/
int nghttp2_session_on_push_promise_received(nghttp2_session *session,
nghttp2_frame *frame);
/*
* Called when PING is received, assuming |frame| is properly
* initialized.
......
......@@ -82,3 +82,8 @@ void nghttp2_stream_update_initial_window_size(nghttp2_stream *stream,
stream->window_size =
new_initial_window_size-(old_initial_window_size-stream->window_size);
}
void nghttp2_stream_promise_fulfilled(nghttp2_stream *stream)
{
stream->state = NGHTTP2_STREAM_OPENED;
}
......@@ -56,7 +56,9 @@ typedef enum {
NGHTTP2_STREAM_OPENED,
/* RST_STREAM is received, but somehow we need to keep stream in
memory. */
NGHTTP2_STREAM_CLOSING
NGHTTP2_STREAM_CLOSING,
/* PUSH_PROMISE is received or sent */
NGHTTP2_STREAM_RESERVED
} nghttp2_stream_state;
typedef enum {
......@@ -155,4 +157,11 @@ void nghttp2_stream_update_initial_window_size(nghttp2_stream *stream,
int32_t new_initial_window_size,
int32_t old_initial_window_size);
/*
* Call this function if promised stream |stream| is replied with
* HEADERS. This function makes the state of the |stream| to
* NGHTTP2_STREAM_OPENED.
*/
void nghttp2_stream_promise_fulfilled(nghttp2_stream *stream);
#endif /* NGHTTP2_STREAM */
......@@ -186,6 +186,39 @@ int nghttp2_submit_settings(nghttp2_session *session,
return r;
}
int nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags,
int32_t stream_id, const char **nv)
{
nghttp2_frame *frame;
nghttp2_nv *nva;
ssize_t nvlen;
uint8_t flags_copy;
int r;
if(!nghttp2_frame_nv_check_null(nv)) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
frame = malloc(sizeof(nghttp2_frame));
if(frame == NULL) {
return NGHTTP2_ERR_NOMEM;
}
nvlen = nghttp2_nv_array_from_cstr(&nva, nv);
if(nvlen < 0) {
free(frame);
return nvlen;
}
/* TODO Implement header continuation */
flags_copy = NGHTTP2_FLAG_END_PUSH_PROMISE;
nghttp2_frame_push_promise_init(&frame->push_promise, flags_copy,
stream_id, -1, nva, nvlen);
r = nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL);
if(r != 0) {
nghttp2_frame_push_promise_free(&frame->push_promise);
free(frame);
}
return 0;
}
int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags,
int32_t stream_id,
int32_t window_size_increment)
......
......@@ -90,12 +90,16 @@ int main(int argc, char* argv[])
test_nghttp2_session_on_syn_reply_received) ||
!CU_add_test(pSuite, "session_on_headers_received",
test_nghttp2_session_on_headers_received) ||
!CU_add_test(pSuite, "session_on_push_reply_received",
test_nghttp2_session_on_push_reply_received) ||
!CU_add_test(pSuite, "session_on_priority_received",
test_nghttp2_session_on_priority_received) ||
!CU_add_test(pSuite, "session_on_rst_stream_received",
test_nghttp2_session_on_rst_stream_received) ||
!CU_add_test(pSuite, "session_on_settings_received",
test_nghttp2_session_on_settings_received) ||
!CU_add_test(pSuite, "session_on_push_promise_received",
test_nghttp2_session_on_push_promise_received) ||
!CU_add_test(pSuite, "session_on_ping_received",
test_nghttp2_session_on_ping_received) ||
!CU_add_test(pSuite, "session_on_goaway_received",
......@@ -110,10 +114,14 @@ int main(int argc, char* argv[])
test_nghttp2_session_send_headers_reply) ||
!CU_add_test(pSuite, "session_send_headers_header_comp_error",
test_nghttp2_session_send_headers_header_comp_error) ||
!CU_add_test(pSuite, "session_send_headers_push_reply",
test_nghttp2_session_send_headers_push_reply) ||
!CU_add_test(pSuite, "session_send_priority",
test_nghttp2_session_send_priority) ||
!CU_add_test(pSuite, "session_send_rst_stream",
test_nghttp2_session_send_rst_stream) ||
!CU_add_test(pSuite, "session_send_push_promise",
test_nghttp2_session_send_push_promise) ||
!CU_add_test(pSuite, "session_is_my_stream_id",
test_nghttp2_session_is_my_stream_id) ||
!CU_add_test(pSuite, "submit_response", test_nghttp2_submit_response) ||
......@@ -127,14 +135,20 @@ int main(int argc, char* argv[])
test_nghttp2_submit_headers_start_stream) ||
!CU_add_test(pSuite, "submit_headers_reply",
test_nghttp2_submit_headers_reply) ||
!CU_add_test(pSuite, "submit_headers_push_reply",
test_nghttp2_submit_headers_push_reply) ||
!CU_add_test(pSuite, "submit_headers", test_nghttp2_submit_headers) ||
!CU_add_test(pSuite, "submit_priority", test_nghttp2_submit_priority) ||
!CU_add_test(pSuite, "session_submit_settings",
test_nghttp2_submit_settings) ||
!CU_add_test(pSuite, "session_submit_push_promise",
test_nghttp2_submit_push_promise) ||
!CU_add_test(pSuite, "submit_window_update",
test_nghttp2_submit_window_update) ||
!CU_add_test(pSuite, "submit_invalid_nv",
test_nghttp2_submit_invalid_nv) ||
!CU_add_test(pSuite, "session_open_stream",
test_nghttp2_session_open_stream) ||
!CU_add_test(pSuite, "session_get_next_ob_item",
test_nghttp2_session_get_next_ob_item) ||
!CU_add_test(pSuite, "session_pop_next_ob_item",
......@@ -182,6 +196,8 @@ int main(int argc, char* argv[])
test_nghttp2_frame_pack_rst_stream) ||
!CU_add_test(pSuite, "frame_pack_settings",
test_nghttp2_frame_pack_settings) ||
!CU_add_test(pSuite, "frame_pack_push_promise",
test_nghttp2_frame_pack_push_promise) ||
!CU_add_test(pSuite, "frame_pack_ping", test_nghttp2_frame_pack_ping) ||
!CU_add_test(pSuite, "frame_pack_goaway",
test_nghttp2_frame_pack_goaway) ||
......
......@@ -129,7 +129,6 @@ void test_nghttp2_frame_pack_headers()
/* We didn't include PRIORITY flag so priority is not packed */
CU_ASSERT(1 << 30 == oframe.pri);
CU_ASSERT(7 == oframe.nvlen);
CU_ASSERT(memcmp("method", oframe.nva[0].name, oframe.nva[0].namelen) == 0);
CU_ASSERT(nvnameeq("method", &oframe.nva[0]));
CU_ASSERT(nvvalueeq("GET", &oframe.nva[0]));
......@@ -263,6 +262,43 @@ void test_nghttp2_frame_pack_settings()
nghttp2_frame_settings_free(&oframe);
}
void test_nghttp2_frame_pack_push_promise()
{
nghttp2_hd_context deflater, inflater;
nghttp2_push_promise frame, oframe;
uint8_t *buf = NULL;
size_t buflen = 0;
ssize_t framelen;
nghttp2_nv *nva;
ssize_t nvlen;
nghttp2_hd_deflate_init(&deflater, NGHTTP2_HD_SIDE_CLIENT);
nghttp2_hd_inflate_init(&inflater, NGHTTP2_HD_SIDE_SERVER);
nvlen = nghttp2_nv_array_from_cstr(&nva, headers);
nghttp2_frame_push_promise_init(&frame, NGHTTP2_FLAG_END_PUSH_PROMISE,
1000000007, (1U << 31) - 1, nva, nvlen);
framelen = nghttp2_frame_pack_push_promise(&buf, &buflen, &frame, &deflater);
CU_ASSERT(0 == unpack_frame_with_nv_block((nghttp2_frame*)&oframe,
NGHTTP2_PUSH_PROMISE,
&inflater,
buf, framelen));
check_frame_header(framelen - NGHTTP2_FRAME_HEAD_LENGTH,
NGHTTP2_PUSH_PROMISE,
NGHTTP2_FLAG_END_PUSH_PROMISE, 1000000007, &oframe.hd);
CU_ASSERT((1U << 31) - 1 == oframe.promised_stream_id);
CU_ASSERT(7 == oframe.nvlen);
CU_ASSERT(nvnameeq("method", &oframe.nva[0]));
CU_ASSERT(nvvalueeq("GET", &oframe.nva[0]));
free(buf);
nghttp2_frame_push_promise_free(&oframe);
nghttp2_frame_push_promise_free(&frame);
nghttp2_hd_inflate_free(&inflater);
nghttp2_hd_deflate_free(&deflater);
}
void test_nghttp2_frame_pack_ping(void)
{
nghttp2_ping frame, oframe;
......
......@@ -33,6 +33,7 @@ void test_nghttp2_frame_pack_headers_frame_too_large(void);
void test_nghttp2_frame_pack_priority(void);
void test_nghttp2_frame_pack_rst_stream(void);
void test_nghttp2_frame_pack_settings(void);
void test_nghttp2_frame_pack_push_promise(void);
void test_nghttp2_frame_pack_ping(void);
void test_nghttp2_frame_pack_goaway(void);
void test_nghttp2_frame_pack_window_update(void);
......
This diff is collapsed.
......@@ -34,9 +34,11 @@ void test_nghttp2_session_add_frame(void);
void test_nghttp2_session_on_syn_stream_received(void);
void test_nghttp2_session_on_syn_reply_received(void);
void test_nghttp2_session_on_headers_received(void);
void test_nghttp2_session_on_push_reply_received(void);
void test_nghttp2_session_on_priority_received(void);
void test_nghttp2_session_on_rst_stream_received(void);
void test_nghttp2_session_on_settings_received(void);
void test_nghttp2_session_on_push_promise_received(void);
void test_nghttp2_session_on_ping_received(void);
void test_nghttp2_session_on_goaway_received(void);
void test_nghttp2_session_on_window_update_received(void);
......@@ -44,8 +46,10 @@ void test_nghttp2_session_on_data_received(void);
void test_nghttp2_session_send_headers_start_stream(void);
void test_nghttp2_session_send_headers_reply(void);
void test_nghttp2_session_send_headers_header_comp_error(void);
void test_nghttp2_session_send_headers_push_reply(void);
void test_nghttp2_session_send_priority(void);
void test_nghttp2_session_send_rst_stream(void);
void test_nghttp2_session_send_push_promise(void);
void test_nghttp2_session_is_my_stream_id(void);
void test_nghttp2_submit_response(void);
void test_nghttp2_submit_response_without_data(void);
......@@ -53,11 +57,14 @@ void test_nghttp2_submit_request_with_data(void);
void test_nghttp2_submit_request_without_data(void);
void test_nghttp2_submit_headers_start_stream(void);
void test_nghttp2_submit_headers_reply(void);
void test_nghttp2_submit_headers_push_reply(void);
void test_nghttp2_submit_headers(void);
void test_nghttp2_submit_priority(void);
void test_nghttp2_submit_settings(void);
void test_nghttp2_submit_push_promise(void);
void test_nghttp2_submit_window_update(void);
void test_nghttp2_submit_invalid_nv(void);
void test_nghttp2_session_open_stream(void);
void test_nghttp2_session_get_next_ob_item(void);
void test_nghttp2_session_pop_next_ob_item(void);
void test_nghttp2_session_reply_fail(void);
......
......@@ -44,6 +44,14 @@ ssize_t unpack_frame_with_nv_block(nghttp2_frame *frame,
len - NGHTTP2_FRAME_HEAD_LENGTH,
inflater);
break;
case NGHTTP2_PUSH_PROMISE:
rv = nghttp2_frame_unpack_push_promise
((nghttp2_push_promise*)frame,
&in[0], NGHTTP2_FRAME_HEAD_LENGTH,
&in[NGHTTP2_FRAME_HEAD_LENGTH],
len - NGHTTP2_FRAME_HEAD_LENGTH,
inflater);
break;
default:
/* Must not be reachable */
assert(0);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment