Implement server push

This commit is contained in:
Tatsuhiro Tsujikawa 2013-07-25 01:49:05 +09:00
parent 321136b042
commit 84cbebf4f7
15 changed files with 1049 additions and 68 deletions

View File

@ -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
*

View File

@ -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)
{

View File

@ -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

View File

@ -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;

View File

@ -72,6 +72,14 @@ static int nghttp2_is_fatal(int error)
return error < NGHTTP2_ERR_FATAL;
}
/* Returns the pushed stream's priority based on the associated stream
|stream|. */
static int32_t nghttp2_pushed_stream_pri(nghttp2_stream *stream)
{
return stream->pri == NGHTTP2_PRI_LOWEST ?
(int32_t)NGHTTP2_PRI_LOWEST : stream->pri + 1;
}
int nghttp2_session_fail_session(nghttp2_session *session,
nghttp2_error_code error_code)
{
@ -318,6 +326,7 @@ int nghttp2_session_add_frame(nghttp2_session *session,
item->pri = NGHTTP2_PRI_DEFAULT;
if(frame_cat == NGHTTP2_CAT_CTRL) {
nghttp2_frame *frame = (nghttp2_frame*)abs_frame;
nghttp2_stream *stream = NULL;
switch(frame->hd.type) {
case NGHTTP2_HEADERS:
if(frame->hd.stream_id == -1) {
@ -326,7 +335,6 @@ int nghttp2_session_add_frame(nghttp2_session *session,
} else {
/* Otherwise, the frame must have stream ID. We use its
priority value. */
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if(stream) {
item->pri = stream->pri;
@ -334,7 +342,6 @@ int nghttp2_session_add_frame(nghttp2_session *session,
}
break;
case NGHTTP2_PRIORITY: {
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if(stream) {
item->pri = stream->pri;
@ -342,7 +349,6 @@ int nghttp2_session_add_frame(nghttp2_session *session,
break;
}
case NGHTTP2_RST_STREAM: {
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if(stream) {
stream->state = NGHTTP2_STREAM_CLOSING;
@ -354,6 +360,14 @@ int nghttp2_session_add_frame(nghttp2_session *session,
/* Should NGHTTP2_SETTINGS have higher priority? */
item->pri = -1;
break;
case NGHTTP2_PUSH_PROMISE: {
/* Use priority of associated stream */
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if(stream) {
item->pri = stream->pri;
}
break;
}
case NGHTTP2_PING:
/* Ping has highest priority. */
item->pri = NGHTTP2_OB_PRI_PING;
@ -366,7 +380,6 @@ int nghttp2_session_add_frame(nghttp2_session *session,
/* Connection level window update should have higher priority */
item->pri = -1;
} else {
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if(stream) {
item->pri = stream->pri;
@ -374,7 +387,13 @@ int nghttp2_session_add_frame(nghttp2_session *session,
}
break;
}
if(frame->hd.type == NGHTTP2_HEADERS && frame->hd.stream_id == -1) {
if(frame->hd.type == NGHTTP2_HEADERS &&
(frame->hd.stream_id == -1 ||
(stream && stream->state == NGHTTP2_STREAM_RESERVED &&
session->server))) {
/* TODO If 2 HEADERS are submitted for reserved stream, then
both of them are queued into ob_ss_pq, which is not
desirable. */
r = nghttp2_pq_push(&session->ob_ss_pq, item);
} else {
r = nghttp2_pq_push(&session->ob_pq, item);
@ -442,13 +461,25 @@ nghttp2_stream* nghttp2_session_open_stream(nghttp2_session *session,
r = nghttp2_map_insert(&session->streams, &stream->map_entry);
if(r != 0) {
free(stream);
stream = NULL;
return NULL;
}
if(initial_state == NGHTTP2_STREAM_RESERVED) {
if(nghttp2_session_is_my_stream_id(session, stream_id)) {
/* half closed (remote) */
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
} else {
/* half closed (local) */
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
}
/* Reserved stream does not count in the concurrent streams
limit. That is one of the DOS vector. */
} else {
if(nghttp2_session_is_my_stream_id(session, stream_id)) {
++session->num_outgoing_streams;
} else {
++session->num_incoming_streams;
}
}
return stream;
}
@ -458,16 +489,21 @@ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id,
nghttp2_stream *stream = nghttp2_session_get_stream(session, stream_id);
if(stream) {
if(stream->state != NGHTTP2_STREAM_INITIAL &&
stream->state != NGHTTP2_STREAM_RESERVED &&
/* TODO Should on_stream_close_callback be called against
NGHTTP2_STREAM_RESERVED? It is actually not opened yet. */
session->callbacks.on_stream_close_callback) {
session->callbacks.on_stream_close_callback(session, stream_id,
error_code,
session->user_data);
}
if(stream->state != NGHTTP2_STREAM_RESERVED) {
if(nghttp2_session_is_my_stream_id(session, stream_id)) {
--session->num_outgoing_streams;
} else {
--session->num_incoming_streams;
}
}
nghttp2_map_remove(&session->streams, stream_id);
nghttp2_stream_free(stream);
free(stream);
@ -580,6 +616,43 @@ static int nghttp2_session_predicate_syn_reply_send(nghttp2_session *session,
}
}
/*
* This function checks HEADERS for reserved stream can be sent. The
* stream |stream_id| must be reserved state and the |session| is
* server side.
*
* This function returns 0 if it succeeds, or one of the following
* error codes:
*
* NGHTTP2_ERR_STREAM_CLOSED
* The stream is already closed.
* NGHTTP2_ERR_STREAM_SHUT_WR
* The stream is half-closed for transmission.
* NGHTTP2_ERR_PROTO
* The session is client-side and/or stream is not reserved state
* NGHTTP2_ERR_STREAM_CLOSED
* RST_STREAM was queued for this stream.
*/
static int nghttp2_session_predicate_push_reply_send(nghttp2_session *session,
int32_t stream_id)
{
nghttp2_stream *stream = nghttp2_session_get_stream(session, stream_id);
int r;
/* TODO Should disallow HEADERS if GOAWAY has already been issued? */
r = nghttp2_predicate_stream_for_send(stream);
if(r != 0) {
return r;
}
if(!session->server || stream->state != NGHTTP2_STREAM_RESERVED) {
/* Only server can send HEADERS for reserved streams */
return NGHTTP2_ERR_PROTO;
}
if(stream->state == NGHTTP2_STREAM_CLOSING) {
return NGHTTP2_ERR_STREAM_CLOSING;
}
return 0;
}
/*
* This function checks frames belongs to the stream |stream_id| can
* be sent.
@ -645,6 +718,60 @@ static int nghttp2_session_predicate_priority_send
return nghttp2_session_predicate_stream_frame_send(session, stream_id);
}
/*
* This function checks PUSH_PROMISE frame |frame| with stream ID
* |stream_id| can be sent at this time.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* NGHTTP2_ERR_START_STREAM_NOT_ALLOWED
* New stream cannot be created because GOAWAY is already sent or
* received.
* NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE
* Stream ID has reached the maximum value. Therefore no stream ID
* is available.
* NGHTTP2_ERR_PROTO
* The client side attempts to send PUSH_PROMISE, or the server
* sends PUSH_PROMISE for the stream not initiated by the client.
* NGHTTP2_ERR_STREAM_CLOSED
* The stream is already closed or does not exist.
* NGHTTP2_ERR_STREAM_CLOSING
* RST_STREAM was queued for this stream.
* NGHTTP2_ERR_STREAM_SHUT_WR
* The transmission is not allowed for this stream (e.g., a frame
* with END_STREAM flag set has already sent)
*/
static int nghttp2_session_predicate_push_promise_send
(nghttp2_session *session, int32_t stream_id)
{
int rv;
nghttp2_stream *stream;
if(!session->server || nghttp2_session_is_my_stream_id(session, stream_id)) {
/* Only server is allowed to push. And associated stream must be
created from client side */
return NGHTTP2_ERR_PROTO;
}
stream = nghttp2_session_get_stream(session, stream_id);
rv = nghttp2_predicate_stream_for_send(stream);
if(rv != 0) {
return rv;
}
if(stream->state == NGHTTP2_STREAM_CLOSING) {
return NGHTTP2_ERR_STREAM_CLOSING;
}
if(session->goaway_flags) {
/* When GOAWAY is sent or received, peer must not promise new
stream ID */
return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
}
/* All 32bit signed stream IDs are spent. */
if(session->next_stream_id > INT32_MAX) {
return NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE;
}
return 0;
}
/*
* This function checks WINDOW_UPDATE with the stream ID |stream_id|
* can be sent at this time. Note that FIN flag of the previous frame
@ -670,7 +797,8 @@ static int nghttp2_session_predicate_window_update_send
if(stream == NULL) {
return NGHTTP2_ERR_STREAM_CLOSED;
}
if(stream->state != NGHTTP2_STREAM_CLOSING) {
if(stream->state != NGHTTP2_STREAM_CLOSING &&
stream->state != NGHTTP2_STREAM_RESERVED) {
return 0;
} else {
return NGHTTP2_ERR_STREAM_CLOSING;
@ -748,7 +876,8 @@ static int nghttp2_session_predicate_data_send(nghttp2_session *session,
frames are sent. This is not desirable situation; we want to
close stream as soon as possible. To achieve this, we remove
DATA frame before RST_STREAM. */
if(stream->state != NGHTTP2_STREAM_CLOSING) {
if(stream->state != NGHTTP2_STREAM_CLOSING &&
stream->state != NGHTTP2_STREAM_RESERVED) {
return 0;
} else {
return NGHTTP2_ERR_STREAM_CLOSING;
@ -772,7 +901,8 @@ static ssize_t nghttp2_session_prep_frame(nghttp2_session *session,
nghttp2_frame *frame;
frame = nghttp2_outbound_item_get_ctrl_frame(item);
switch(frame->hd.type) {
case NGHTTP2_HEADERS:
case NGHTTP2_HEADERS: {
int push_reply = 0;
if(frame->hd.stream_id == -1) {
/* initial HEADERS, which opens stream */
int r;
@ -784,6 +914,11 @@ static ssize_t nghttp2_session_prep_frame(nghttp2_session *session,
}
frame->hd.stream_id = session->next_stream_id;
session->next_stream_id += 2;
} else if(nghttp2_session_predicate_push_reply_send
(session, frame->hd.stream_id) == 0) {
/* HEADERS against promised stream */
push_reply = 1;
frame->headers.cat = NGHTTP2_HCAT_REPLY;
} else if(nghttp2_session_predicate_syn_reply_send
(session, frame->hd.stream_id) == 0) {
/* first response HEADERS */
@ -816,8 +951,17 @@ static ssize_t nghttp2_session_prep_frame(nghttp2_session *session,
aux_data ? aux_data->stream_user_data : NULL) == NULL) {
return NGHTTP2_ERR_NOMEM;
}
} else if(push_reply) {
nghttp2_headers_aux_data *aux_data;
aux_data = (nghttp2_headers_aux_data*)item->aux_data;
if(aux_data) {
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
stream->stream_user_data = aux_data->stream_user_data;
}
}
break;
}
case NGHTTP2_PRIORITY: {
int r;
r = nghttp2_session_predicate_priority_send
@ -849,6 +993,36 @@ static ssize_t nghttp2_session_prep_frame(nghttp2_session *session,
return framebuflen;
}
break;
case NGHTTP2_PUSH_PROMISE: {
int r;
nghttp2_stream *stream;
r = nghttp2_session_predicate_push_promise_send(session,
frame->hd.stream_id);
if(r != 0) {
return r;
}
frame->push_promise.promised_stream_id = session->next_stream_id;
session->next_stream_id += 2;
framebuflen = nghttp2_frame_pack_push_promise(&session->aob.framebuf,
&session->aob.framebufmax,
&frame->push_promise,
&session->hd_deflater);
nghttp2_hd_end_headers(&session->hd_deflater);
if(framebuflen < 0) {
return framebuflen;
}
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
assert(stream);
if(nghttp2_session_open_stream
(session, frame->push_promise.promised_stream_id,
frame->hd.flags,
nghttp2_pushed_stream_pri(stream),
NGHTTP2_STREAM_RESERVED,
NULL) == NULL) {
return NGHTTP2_ERR_NOMEM;
}
break;
}
case NGHTTP2_PING:
framebuflen = nghttp2_frame_pack_ping(&session->aob.framebuf,
&session->aob.framebufmax,
@ -1137,6 +1311,9 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session)
case NGHTTP2_SETTINGS:
/* nothing to do */
break;
case NGHTTP2_PUSH_PROMISE:
/* nothing to do */
break;
case NGHTTP2_PING:
/* nothing to do */
break;
@ -1525,20 +1702,15 @@ int nghttp2_session_on_syn_reply_received(nghttp2_session *session,
nghttp2_frame *frame,
nghttp2_stream *stream)
{
int r = 0;
int valid = 0;
nghttp2_error_code error_code = NGHTTP2_PROTOCOL_ERROR;
/* This function is only called if stream->state ==
NGHTTP2_STREAM_OPENING and stream_id is local side initiated. */
assert(stream->state == NGHTTP2_STREAM_OPENING &&
nghttp2_session_is_my_stream_id(session, frame->hd.stream_id));
if(frame->hd.stream_id == 0) {
return nghttp2_session_handle_invalid_connection(session, frame,
NGHTTP2_PROTOCOL_ERROR);
}
if((stream->shut_flags & NGHTTP2_SHUT_RD) == 0) {
if(nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
/* This function is only called if stream->state ==
NGHTTP2_STREAM_OPENING. If server push is implemented, it may
be called on reserved state. */
assert(stream->state == NGHTTP2_STREAM_OPENING);
valid = 1;
stream->state = NGHTTP2_STREAM_OPENED;
nghttp2_session_call_on_frame_received(session, frame);
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
@ -1547,7 +1719,6 @@ int nghttp2_session_on_syn_reply_received(nghttp2_session *session,
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
nghttp2_session_close_stream_if_shut_rdwr(session, stream);
}
}
} else {
/* half closed (remote): from the spec:
@ -1555,12 +1726,34 @@ int nghttp2_session_on_syn_reply_received(nghttp2_session *session,
in this state it MUST respond with a stream error (Section
5.4.2) of type STREAM_CLOSED.
*/
error_code = NGHTTP2_STREAM_CLOSED;
return nghttp2_session_handle_invalid_stream(session, frame,
NGHTTP2_STREAM_CLOSED);
}
if(!valid) {
r = nghttp2_session_handle_invalid_stream(session, frame, error_code);
return 0;
}
return r;
int nghttp2_session_on_push_reply_received(nghttp2_session *session,
nghttp2_frame *frame,
nghttp2_stream *stream)
{
int rv = 0;
assert(!session->server && stream->state == NGHTTP2_STREAM_RESERVED);
if(frame->hd.stream_id == 0) {
return nghttp2_session_handle_invalid_connection(session, frame,
NGHTTP2_PROTOCOL_ERROR);
}
if(session->goaway_flags) {
/* We don't accept new stream after GOAWAY is sent or received. */
return 0;
}
rv = nghttp2_session_validate_syn_stream(session, &frame->headers);
if(rv != 0) {
return nghttp2_session_handle_invalid_stream(session, frame, rv);
}
nghttp2_stream_promise_fulfilled(stream);
++session->num_incoming_streams;
nghttp2_session_call_on_frame_received(session, frame);
return 0;
}
int nghttp2_session_on_headers_received(nghttp2_session *session,
@ -1815,6 +2008,69 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
return 0;
}
int nghttp2_session_on_push_promise_received(nghttp2_session *session,
nghttp2_frame *frame)
{
nghttp2_stream *stream;
if(session->server || frame->hd.stream_id == 0) {
return nghttp2_session_handle_invalid_connection(session, frame,
NGHTTP2_PROTOCOL_ERROR);
}
if(session->goaway_flags) {
/* We just dicard PUSH_PROMISE after GOAWAY is sent or
received. */
return 0;
}
if(!nghttp2_session_is_new_peer_stream_id
(session, frame->push_promise.promised_stream_id)) {
/* The spec says if an endpoint receives a HEADERS with invalid
stream ID, it MUST issue connection error with error code
PROTOCOL_ERROR. It applies to PUSH_PROMISE too. */
return nghttp2_session_handle_invalid_connection
(session, frame, NGHTTP2_PROTOCOL_ERROR);
}
session->last_recv_stream_id = frame->push_promise.promised_stream_id;
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if(stream) {
if((stream->shut_flags & NGHTTP2_SHUT_RD) == 0) {
if(stream->state == NGHTTP2_STREAM_CLOSING) {
return nghttp2_session_add_rst_stream
(session,
frame->push_promise.promised_stream_id,
NGHTTP2_REFUSED_STREAM);
} else {
nghttp2_stream *promised_stream;
promised_stream = nghttp2_session_open_stream
(session,
frame->push_promise.promised_stream_id,
frame->hd.flags,
nghttp2_pushed_stream_pri(stream),
NGHTTP2_STREAM_RESERVED,
NULL);
if(!promised_stream) {
return NGHTTP2_ERR_NOMEM;
}
nghttp2_session_call_on_frame_received(session, frame);
}
} else {
if(session->callbacks.on_invalid_frame_recv_callback) {
session->callbacks.on_invalid_frame_recv_callback
(session, frame, NGHTTP2_PROTOCOL_ERROR, session->user_data);
}
return nghttp2_session_add_rst_stream
(session,
frame->push_promise.promised_stream_id,
NGHTTP2_PROTOCOL_ERROR);
}
} else {
return nghttp2_session_add_rst_stream
(session,
frame->push_promise.promised_stream_id,
NGHTTP2_REFUSED_STREAM);
}
return 0;
}
int nghttp2_session_on_ping_received(nghttp2_session *session,
nghttp2_frame *frame)
{
@ -2009,10 +2265,10 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
r = session->iframe.error_code;
}
if(r == 0) {
if(nghttp2_session_is_my_stream_id(session, frame.hd.stream_id)) {
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, frame.hd.stream_id);
if(stream) {
if(nghttp2_session_is_my_stream_id(session, frame.hd.stream_id)) {
if(stream->state == NGHTTP2_STREAM_OPENING) {
frame.headers.cat = NGHTTP2_HCAT_REPLY;
r = nghttp2_session_on_syn_reply_received(session, &frame, stream);
@ -2020,9 +2276,13 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
frame.headers.cat = NGHTTP2_HCAT_HEADERS;
r = nghttp2_session_on_headers_received(session, &frame, stream);
}
} else if(!session->server &&
stream->state == NGHTTP2_STREAM_RESERVED) {
frame.headers.cat = NGHTTP2_HCAT_REPLY;
r = nghttp2_session_on_push_reply_received(session, &frame, stream);
} else {
r = nghttp2_session_handle_invalid_stream
(session, &frame, NGHTTP2_PROTOCOL_ERROR);
frame.headers.cat = NGHTTP2_HCAT_HEADERS;
r = nghttp2_session_on_headers_received(session, &frame, stream);
}
} else {
frame.headers.cat = NGHTTP2_HCAT_START_STREAM;
@ -2084,6 +2344,26 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
NGHTTP2_PROTOCOL_ERROR);
}
break;
case NGHTTP2_PUSH_PROMISE:
if(session->iframe.error_code == 0) {
r = nghttp2_frame_unpack_push_promise(&frame.push_promise,
session->iframe.headbuf,
sizeof(session->iframe.headbuf),
session->iframe.buf,
session->iframe.buflen,
&session->hd_inflater);
} else {
r = session->iframe.error_code;
}
if(r == 0) {
r = nghttp2_session_on_push_promise_received(session, &frame);
nghttp2_frame_push_promise_free(&frame.push_promise);
nghttp2_hd_end_headers(&session->hd_inflater);
} else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error(session, type, r,
NGHTTP2_PROTOCOL_ERROR);
}
break;
case NGHTTP2_PING:
r = nghttp2_frame_unpack_ping(&frame.ping,
session->iframe.headbuf,

View File

@ -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.

View File

@ -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;
}

View File

@ -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 */

View File

@ -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)

View File

@ -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) ||

View File

@ -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;

View File

@ -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);

View File

@ -441,13 +441,13 @@ void test_nghttp2_session_recv_invalid_frame(void)
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(0 == user_data.frame_send_cb_called);
/* Receive exactly same bytes of SYN_STREAM causes error */
/* Receive exactly same bytes of HEADERS is treated as subsequent
HEADERS (e.g., trailers */
scripted_data_feed_init(&df, framedata, framelen);
CU_ASSERT(0 == nghttp2_session_recv(session));
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(1 == user_data.frame_send_cb_called);
CU_ASSERT(NGHTTP2_GOAWAY == user_data.sent_frame_type);
CU_ASSERT(0 == user_data.frame_send_cb_called);
free(framedata);
nghttp2_frame_headers_free(&frame.headers);
@ -671,7 +671,6 @@ void test_nghttp2_session_on_syn_reply_received(void)
const char *nv[] = { NULL };
nghttp2_frame frame;
nghttp2_stream *stream;
nghttp2_outbound_item *item;
nghttp2_nv *nva;
ssize_t nvlen;
@ -694,21 +693,6 @@ void test_nghttp2_session_on_syn_reply_received(void)
CU_ASSERT(1 == user_data.frame_recv_cb_called);
CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state);
/* Check the situation when response HEADERS is received after peer
sends FIN */
stream = nghttp2_session_open_stream(session, 3, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_OPENED, NULL);
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
frame.hd.stream_id = 3;
CU_ASSERT(0 == nghttp2_session_on_syn_reply_received(session, &frame,
stream));
CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called);
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_RST_STREAM == OB_CTRL_TYPE(item));
CU_ASSERT(NGHTTP2_STREAM_CLOSED == OB_CTRL(item)->rst_stream.error_code);
nghttp2_frame_headers_free(&frame.headers);
nghttp2_session_del(session);
}
@ -784,6 +768,55 @@ void test_nghttp2_session_on_headers_received(void)
nghttp2_session_del(session);
}
void test_nghttp2_session_on_push_reply_received(void)
{
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
my_user_data user_data;
const char *nv[] = { NULL };
nghttp2_frame frame;
nghttp2_stream *stream;
nghttp2_nv *nva;
ssize_t nvlen;
nghttp2_outbound_item *item;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.on_frame_recv_callback = on_frame_recv_callback;
callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
user_data.frame_recv_cb_called = 0;
user_data.invalid_frame_recv_cb_called = 0;
nghttp2_session_client_new(&session, &callbacks, &user_data);
stream = nghttp2_session_open_stream(session, 2, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_RESERVED, NULL);
nvlen = nghttp2_nv_array_from_cstr(&nva, nv);
nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2,
NGHTTP2_PRI_DEFAULT, nva, nvlen);
/* nghttp2_session_on_push_reply_received assumes stream's state is
NGHTTP2_STREAM_RESERVED and session->server is 0. */
CU_ASSERT(0 == nghttp2_session_on_push_reply_received(session, &frame,
stream));
CU_ASSERT(1 == user_data.frame_recv_cb_called);
CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state);
CU_ASSERT(1 == session->num_incoming_streams);
/* If max concurrent streams limit is exceeded, RST_STREAMed */
session->local_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] = 1;
stream = nghttp2_session_open_stream(session, 4, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_RESERVED, NULL);
frame.hd.stream_id = 4;
CU_ASSERT(0 == nghttp2_session_on_push_reply_received(session, &frame,
stream));
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_RST_STREAM == OB_CTRL_TYPE(item));
CU_ASSERT(NGHTTP2_REFUSED_STREAM == OB_CTRL(item)->rst_stream.error_code);
nghttp2_frame_headers_free(&frame.headers);
nghttp2_session_del(session);
}
void test_nghttp2_session_on_priority_received(void)
{
nghttp2_session *session;
@ -896,6 +929,121 @@ void test_nghttp2_session_on_settings_received(void)
nghttp2_session_del(session);
}
void test_nghttp2_session_on_push_promise_received(void)
{
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
my_user_data user_data;
const char *nv[] = { NULL };
nghttp2_frame frame;
nghttp2_stream *stream, *promised_stream;
nghttp2_outbound_item *item;
nghttp2_nv *nva;
ssize_t nvlen;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback;
callbacks.on_frame_recv_callback = on_frame_recv_callback;
callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
user_data.frame_recv_cb_called = 0;
user_data.invalid_frame_recv_cb_called = 0;
nghttp2_session_client_new(&session, &callbacks, &user_data);
stream = nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_OPENING, NULL);
nvlen = nghttp2_nv_array_from_cstr(&nva, nv);
nghttp2_frame_push_promise_init(&frame.push_promise,
NGHTTP2_FLAG_END_PUSH_PROMISE, 1, 2,
nva, nvlen);
CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame));
CU_ASSERT(1 == user_data.frame_recv_cb_called);
promised_stream = nghttp2_session_get_stream(session, 2);
CU_ASSERT(NGHTTP2_STREAM_RESERVED == promised_stream->state);
CU_ASSERT(2 == session->last_recv_stream_id);
/* Attempt to PUSH_PROMISE against half close (remote) */
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
frame.push_promise.promised_stream_id = 4;
user_data.frame_recv_cb_called = 0;
user_data.invalid_frame_recv_cb_called = 0;
CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame));
CU_ASSERT(0 == user_data.frame_recv_cb_called);
CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called);
CU_ASSERT(NULL == nghttp2_session_get_stream(session, 4));
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_RST_STREAM == OB_CTRL_TYPE(item));
CU_ASSERT(4 == OB_CTRL(item)->hd.stream_id);
CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == OB_CTRL(item)->rst_stream.error_code);
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(4 == session->last_recv_stream_id);
/* Attempt to PUSH_PROMISE against stream in closing state */
stream->shut_flags = NGHTTP2_SHUT_NONE;
stream->state = NGHTTP2_STREAM_CLOSING;
frame.push_promise.promised_stream_id = 6;
user_data.frame_recv_cb_called = 0;
user_data.invalid_frame_recv_cb_called = 0;
CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame));
CU_ASSERT(0 == user_data.frame_recv_cb_called);
CU_ASSERT(NULL == nghttp2_session_get_stream(session, 6));
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_RST_STREAM == OB_CTRL_TYPE(item));
CU_ASSERT(6 == OB_CTRL(item)->hd.stream_id);
CU_ASSERT(NGHTTP2_REFUSED_STREAM == OB_CTRL(item)->rst_stream.error_code);
CU_ASSERT(0 == nghttp2_session_send(session));
/* Attempt to PUSH_PROMISE against non-existent stream */
frame.hd.stream_id = 3;
frame.push_promise.promised_stream_id = 8;
user_data.frame_recv_cb_called = 0;
user_data.invalid_frame_recv_cb_called = 0;
CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame));
CU_ASSERT(0 == user_data.frame_recv_cb_called);
CU_ASSERT(NULL == nghttp2_session_get_stream(session, 8));
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_RST_STREAM == OB_CTRL_TYPE(item));
CU_ASSERT(8 == OB_CTRL(item)->hd.stream_id);
CU_ASSERT(NGHTTP2_REFUSED_STREAM == OB_CTRL(item)->rst_stream.error_code);
CU_ASSERT(0 == nghttp2_session_send(session));
/* Same ID twice */
stream->state = NGHTTP2_STREAM_OPENING;
user_data.frame_recv_cb_called = 0;
user_data.invalid_frame_recv_cb_called = 0;
CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame));
CU_ASSERT(0 == user_data.frame_recv_cb_called);
CU_ASSERT(NULL == nghttp2_session_get_stream(session, 8));
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item));
CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == OB_CTRL(item)->goaway.error_code);
CU_ASSERT(0 == nghttp2_session_send(session));
/* After GOAWAY, PUSH_PROMISE will be discarded */
frame.push_promise.promised_stream_id = 10;
user_data.frame_recv_cb_called = 0;
user_data.invalid_frame_recv_cb_called = 0;
CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame));
CU_ASSERT(0 == user_data.frame_recv_cb_called);
CU_ASSERT(NULL == nghttp2_session_get_stream(session, 10));
CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
nghttp2_frame_push_promise_free(&frame.push_promise);
nghttp2_session_del(session);
}
void test_nghttp2_session_on_ping_received(void)
{
nghttp2_session *session;
@ -1150,6 +1298,34 @@ void test_nghttp2_session_send_headers_header_comp_error(void)
nghttp2_session_del(session);
}
void test_nghttp2_session_send_headers_push_reply(void)
{
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
const char *nv[] = { NULL };
nghttp2_frame *frame = malloc(sizeof(nghttp2_frame));
nghttp2_stream *stream;
nghttp2_nv *nva;
ssize_t nvlen;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback;
CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, NULL));
nghttp2_session_open_stream(session, 2, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_RESERVED, NULL);
nvlen = nghttp2_nv_array_from_cstr(&nva, nv);
nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS, 2,
NGHTTP2_PRI_DEFAULT, nva, nvlen);
nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL);
CU_ASSERT(0 == nghttp2_session_send(session));
stream = nghttp2_session_get_stream(session, 2);
CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state);
nghttp2_session_del(session);
}
void test_nghttp2_session_send_priority(void)
{
nghttp2_session *session;
@ -1196,6 +1372,53 @@ void test_nghttp2_session_send_rst_stream(void)
nghttp2_session_del(session);
}
void test_nghttp2_session_send_push_promise(void)
{
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
const char *nv[] = { NULL };
nghttp2_frame *frame = malloc(sizeof(nghttp2_frame));
nghttp2_stream *stream;
nghttp2_nv *nva;
ssize_t nvlen;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback;
nghttp2_session_server_new(&session, &callbacks, NULL);
nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT, NGHTTP2_STREAM_OPENING,
NULL);
nvlen = nghttp2_nv_array_from_cstr(&nva, nv);
nghttp2_frame_push_promise_init(&frame->push_promise,
NGHTTP2_FLAG_END_PUSH_PROMISE, 1, -1,
nva, nvlen);
nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL);
CU_ASSERT(0 == nghttp2_session_send(session));
stream = nghttp2_session_get_stream(session, 2);
CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state);
nghttp2_session_del(session);
/* PUSH_PROMISE from client is error */
nghttp2_session_client_new(&session, &callbacks, NULL);
nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT, NGHTTP2_STREAM_OPENING,
NULL);
nvlen = nghttp2_nv_array_from_cstr(&nva, nv);
frame = malloc(sizeof(nghttp2_frame));
nghttp2_frame_push_promise_init(&frame->push_promise,
NGHTTP2_FLAG_END_PUSH_PROMISE, 1, -1,
nva, nvlen);
nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL);
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(NULL == nghttp2_session_get_stream(session, 3));
nghttp2_session_del(session);
}
void test_nghttp2_session_is_my_stream_id(void)
{
nghttp2_session *session;
@ -1403,6 +1626,56 @@ void test_nghttp2_submit_headers_reply(void)
nghttp2_session_del(session);
}
void test_nghttp2_submit_headers_push_reply(void)
{
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
const char *nv[] = { ":version", "HTTP/1.1", NULL };
my_user_data ud;
nghttp2_stream *stream;
int foo;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback;
callbacks.on_frame_send_callback = on_frame_send_callback;
CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud));
stream = nghttp2_session_open_stream(session, 2, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_RESERVED, NULL);
CU_ASSERT(0 == nghttp2_submit_headers(session,
NGHTTP2_FLAG_NONE,
2, NGHTTP2_PRI_DEFAULT,
nv, &foo));
ud.frame_send_cb_called = 0;
ud.sent_frame_type = 0;
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(1 == ud.frame_send_cb_called);
CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type);
CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state);
CU_ASSERT(&foo == stream->stream_user_data);
nghttp2_session_del(session);
/* Sending HEADERS from client against stream in reserved state is
error */
CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud));
stream = nghttp2_session_open_stream(session, 2, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_RESERVED, NULL);
CU_ASSERT(0 == nghttp2_submit_headers(session,
NGHTTP2_FLAG_NONE,
2, NGHTTP2_PRI_DEFAULT,
nv, NULL));
ud.frame_send_cb_called = 0;
ud.sent_frame_type = 0;
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(0 == ud.frame_send_cb_called);
nghttp2_session_del(session);
}
void test_nghttp2_submit_headers(void)
{
@ -1543,6 +1816,37 @@ void test_nghttp2_submit_settings(void)
nghttp2_session_del(session);
}
void test_nghttp2_submit_push_promise(void)
{
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
const char *nv[] = { ":version", "HTTP/1.1", NULL };
my_user_data ud;
nghttp2_stream *stream;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback;
callbacks.on_frame_send_callback = on_frame_send_callback;
CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud));
nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_OPENING, NULL);
CU_ASSERT(0 == nghttp2_submit_push_promise(session,
NGHTTP2_FLAG_NONE,
1, nv));
ud.frame_send_cb_called = 0;
ud.sent_frame_type = 0;
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(1 == ud.frame_send_cb_called);
CU_ASSERT(NGHTTP2_PUSH_PROMISE == ud.sent_frame_type);
stream = nghttp2_session_get_stream(session, 2);
CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state);
nghttp2_session_del(session);
}
void test_nghttp2_submit_window_update(void)
{
nghttp2_session *session;
@ -1647,6 +1951,54 @@ void test_nghttp2_submit_invalid_nv(void)
nghttp2_session_del(session);
}
void test_nghttp2_session_open_stream(void)
{
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_stream *stream;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
nghttp2_session_server_new(&session, &callbacks, NULL);
stream = nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE,
1000000007, NGHTTP2_STREAM_OPENED,
NULL);
CU_ASSERT(1 == session->num_incoming_streams);
CU_ASSERT(0 == session->num_outgoing_streams);
CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state);
CU_ASSERT(1000000007 == stream->pri);
CU_ASSERT(NGHTTP2_SHUT_NONE == stream->shut_flags);
stream = nghttp2_session_open_stream(session, 2, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_OPENING,
NULL);
CU_ASSERT(1 == session->num_incoming_streams);
CU_ASSERT(1 == session->num_outgoing_streams);
CU_ASSERT(NGHTTP2_SHUT_NONE == stream->shut_flags);
stream = nghttp2_session_open_stream(session, 4, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_RESERVED,
NULL);
CU_ASSERT(1 == session->num_incoming_streams);
CU_ASSERT(1 == session->num_outgoing_streams);
CU_ASSERT(NGHTTP2_SHUT_RD == stream->shut_flags);
nghttp2_session_del(session);
nghttp2_session_client_new(&session, &callbacks, NULL);
stream = nghttp2_session_open_stream(session, 4, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT,
NGHTTP2_STREAM_RESERVED,
NULL);
CU_ASSERT(0 == session->num_incoming_streams);
CU_ASSERT(0 == session->num_outgoing_streams);
CU_ASSERT(NGHTTP2_SHUT_WR == stream->shut_flags);
nghttp2_session_del(session);
}
void test_nghttp2_session_get_next_ob_item(void)
{
nghttp2_session *session;
@ -1747,6 +2099,18 @@ void test_nghttp2_session_pop_next_ob_item(void)
free(item);
nghttp2_session_del(session);
/* Check that push reply HEADERS are queued into ob_ss_pq */
nghttp2_session_server_new(&session, &callbacks, NULL);
session->remote_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] = 0;
nghttp2_session_open_stream(session, 2, NGHTTP2_FLAG_NONE,
NGHTTP2_PRI_DEFAULT, NGHTTP2_STREAM_RESERVED,
NULL);
CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM,
2, NGHTTP2_PRI_DEFAULT, nv, NULL));
CU_ASSERT(NULL == nghttp2_session_pop_next_ob_item(session));
CU_ASSERT(1 == nghttp2_pq_size(&session->ob_ss_pq));
nghttp2_session_del(session);
}
void test_nghttp2_session_reply_fail(void)

View File

@ -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);

View File

@ -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);