Implement server push
This commit is contained in:
parent
321136b042
commit
84cbebf4f7
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
16
tests/main.c
16
tests/main.c
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue