Add nghttp2_session_continue API function
The NGHTTP2_ERR_PAUSE library error code is introduced to pause the execution of nghttp2_session_mem_recv() when that error code is returned from nghttp2_on_frame_recv_callback or nghttp2_on_data_chunk_recv_callback. If this happens, the parameters available for both callbacks are retained until the application calls nghttp2_session_continue(). The application must retain input bytes which was used to produce the frame. After successful call of nghttp2_session_continue, the application can continue to call nghttp2_session_mem_recv() to process additional data.
This commit is contained in:
parent
bfe7a9af00
commit
21d76dcc75
|
@ -258,6 +258,10 @@ typedef enum {
|
|||
* Insufficient buffer size given to function.
|
||||
*/
|
||||
NGHTTP2_ERR_INSUFF_BUFSIZE = -525,
|
||||
/**
|
||||
* Callback was paused by the application
|
||||
*/
|
||||
NGHTTP2_ERR_PAUSE = -526,
|
||||
/**
|
||||
* The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is
|
||||
* under unexpected condition and cannot process any further data
|
||||
|
@ -827,8 +831,19 @@ typedef ssize_t (*nghttp2_recv_callback)
|
|||
* argument passed in to the call to `nghttp2_session_client_new()` or
|
||||
* `nghttp2_session_server_new()`.
|
||||
*
|
||||
* If the application uses `nghttp2_session_mem_recv()`, it can return
|
||||
* :enum:`NGHTTP2_ERR_PAUSE` to make `nghttp2_session_mem_recv()`
|
||||
* return without processing further input bytes. The |frame|
|
||||
* parameter is retained until `nghttp2_session_continue()` is
|
||||
* called. The application must retain the input bytes which was used
|
||||
* to produce the |frame| parameter, because it may refer to the
|
||||
* memory region included in the input bytes. The application which
|
||||
* returns :enum:`NGHTTP2_ERR_PAUSE` must call
|
||||
* `nghttp2_session_continue()` before `nghttp2_session_mem_recv()`.
|
||||
*
|
||||
* The implementation of this function must return 0 if it
|
||||
* succeeds. If nonzero is returned, it is treated as fatal error and
|
||||
* succeeds. It may return :enum:`NGHTTP2_ERR_PAUSE`. If the other
|
||||
* nonzero value is returned, it is treated as fatal error and
|
||||
* `nghttp2_session_recv()` and `nghttp2_session_send()` functions
|
||||
* immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
|
||||
*/
|
||||
|
@ -868,6 +883,16 @@ typedef int (*nghttp2_on_invalid_frame_recv_callback)
|
|||
* third argument passed in to the call to
|
||||
* `nghttp2_session_client_new()` or `nghttp2_session_server_new()`.
|
||||
*
|
||||
* If the application uses `nghttp2_session_mem_recv()`, it can return
|
||||
* :enum:`NGHTTP2_ERR_PAUSE` to make `nghttp2_session_mem_recv()`
|
||||
* return without processing further input bytes. The |frame|
|
||||
* parameter is retained until `nghttp2_session_continue()` is
|
||||
* called. The application must retain the input bytes which was used
|
||||
* to produce the |frame| parameter, because it may refer to the
|
||||
* memory region included in the input bytes. The application which
|
||||
* returns :enum:`NGHTTP2_ERR_PAUSE` must call
|
||||
* `nghttp2_session_continue()` before `nghttp2_session_mem_recv()`.
|
||||
*
|
||||
* The implementation of this function must return 0 if it
|
||||
* succeeds. If nonzero is returned, it is treated as fatal error and
|
||||
* `nghttp2_session_recv()` and `nghttp2_session_send()` functions
|
||||
|
@ -1391,10 +1416,35 @@ int nghttp2_session_recv(nghttp2_session *session);
|
|||
*
|
||||
* :enum:`NGHTTP2_ERR_NOMEM`
|
||||
* Out of memory.
|
||||
* :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`
|
||||
* The callback function failed.
|
||||
*/
|
||||
ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
|
||||
const uint8_t *in, size_t inlen);
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
* Perform post-processing after `nghttp2_session_mem_recv()` was
|
||||
* paused by :enum:`NGHTTP2_ERR_PAUSE` from
|
||||
* :member:`nghttp2_session_callbacks.on_frame_recv_callback` or
|
||||
* :member:`nghttp2_session_callbacks.on_data_chunk_recv_callback`.
|
||||
*
|
||||
* If this function succeeds, the application can call
|
||||
* `nghttp2_session_mem_recv()` again.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of the following
|
||||
* negative error codes:
|
||||
*
|
||||
*
|
||||
* :enum:`NGHTTP2_ERR_NOMEM`
|
||||
* Out of memory.
|
||||
* :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`
|
||||
* The callback function failed.
|
||||
*
|
||||
*/
|
||||
int nghttp2_session_continue(nghttp2_session *session);
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
|
|
|
@ -122,8 +122,39 @@ static int nghttp2_outbound_item_compar(const void *lhsx, const void *rhsx)
|
|||
}
|
||||
}
|
||||
|
||||
static void nghttp2_inbound_frame_reset(nghttp2_inbound_frame *iframe)
|
||||
static void nghttp2_inbound_frame_reset(nghttp2_session *session)
|
||||
{
|
||||
nghttp2_inbound_frame *iframe = &session->iframe;
|
||||
if(iframe->error_code == NGHTTP2_ERR_PAUSE) {
|
||||
switch(iframe->frame.hd.type) {
|
||||
case NGHTTP2_HEADERS:
|
||||
nghttp2_frame_headers_free(&iframe->frame.headers);
|
||||
nghttp2_hd_end_headers(&session->hd_inflater);
|
||||
break;
|
||||
case NGHTTP2_PRIORITY:
|
||||
nghttp2_frame_priority_free(&iframe->frame.priority);
|
||||
break;
|
||||
case NGHTTP2_RST_STREAM:
|
||||
nghttp2_frame_rst_stream_free(&iframe->frame.rst_stream);
|
||||
break;
|
||||
case NGHTTP2_SETTINGS:
|
||||
nghttp2_frame_settings_free(&iframe->frame.settings);
|
||||
break;
|
||||
case NGHTTP2_PUSH_PROMISE:
|
||||
nghttp2_frame_push_promise_free(&iframe->frame.push_promise);
|
||||
nghttp2_hd_end_headers(&session->hd_inflater);
|
||||
break;
|
||||
case NGHTTP2_PING:
|
||||
nghttp2_frame_ping_free(&iframe->frame.ping);
|
||||
break;
|
||||
case NGHTTP2_GOAWAY:
|
||||
nghttp2_frame_goaway_free(&iframe->frame.goaway);
|
||||
break;
|
||||
case NGHTTP2_WINDOW_UPDATE:
|
||||
nghttp2_frame_window_update_free(&iframe->frame.window_update);
|
||||
break;
|
||||
}
|
||||
}
|
||||
iframe->state = NGHTTP2_RECV_HEAD;
|
||||
iframe->payloadlen = iframe->buflen = iframe->off = 0;
|
||||
iframe->headbufoff = 0;
|
||||
|
@ -214,7 +245,7 @@ static int nghttp2_session_new(nghttp2_session **session_ptr,
|
|||
}
|
||||
(*session_ptr)->iframe.bufmax = NGHTTP2_INITIAL_INBOUND_FRAMEBUF_LENGTH;
|
||||
|
||||
nghttp2_inbound_frame_reset(&(*session_ptr)->iframe);
|
||||
nghttp2_inbound_frame_reset(*session_ptr);
|
||||
|
||||
return 0;
|
||||
|
||||
|
@ -302,6 +333,7 @@ void nghttp2_session_del(nghttp2_session *session)
|
|||
if(session == NULL) {
|
||||
return;
|
||||
}
|
||||
nghttp2_inbound_frame_reset(session);
|
||||
nghttp2_map_each_free(&session->streams, nghttp2_free_streams, NULL);
|
||||
nghttp2_session_ob_pq_free(&session->ob_pq);
|
||||
nghttp2_session_ob_pq_free(&session->ob_ss_pq);
|
||||
|
@ -1627,6 +1659,9 @@ static int nghttp2_session_call_on_frame_received
|
|||
if(session->callbacks.on_frame_recv_callback) {
|
||||
rv = session->callbacks.on_frame_recv_callback(session, frame,
|
||||
session->user_data);
|
||||
if(rv == NGHTTP2_ERR_PAUSE) {
|
||||
return rv;
|
||||
}
|
||||
if(rv != 0) {
|
||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
|
@ -1771,14 +1806,12 @@ int nghttp2_session_on_request_headers_received(nghttp2_session *session,
|
|||
if(!stream) {
|
||||
return NGHTTP2_ERR_NOMEM;
|
||||
}
|
||||
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||
}
|
||||
rv = nghttp2_session_call_on_frame_received(session, frame);
|
||||
if(rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||
rv = nghttp2_session_call_on_request_recv(session, frame->hd.stream_id);
|
||||
}
|
||||
/* Here we assume that stream is not shutdown in NGHTTP2_SHUT_WR */
|
||||
|
@ -1855,7 +1888,20 @@ int nghttp2_session_on_push_response_headers_received(nghttp2_session *session,
|
|||
}
|
||||
nghttp2_stream_promise_fulfilled(stream);
|
||||
++session->num_incoming_streams;
|
||||
return nghttp2_session_call_on_frame_received(session, frame);
|
||||
rv = nghttp2_session_call_on_frame_received(session, frame);
|
||||
if(rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||
/* This is the last frame of this stream, so disallow further
|
||||
receptions. */
|
||||
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||
rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
|
||||
if(rv != 0 && nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nghttp2_session_on_headers_received(nghttp2_session *session,
|
||||
|
@ -2412,17 +2458,15 @@ static int session_on_connection_window_update_received
|
|||
(session, frame, NGHTTP2_FLOW_CONTROL_ERROR);
|
||||
}
|
||||
session->remote_window_size += frame->window_update.window_size_increment;
|
||||
rv = nghttp2_session_call_on_frame_received(session, frame);
|
||||
if(rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
/* To queue the DATA deferred by connection-level flow-control, we
|
||||
have to check all streams. Bad. */
|
||||
if(session->remote_window_size > 0) {
|
||||
return nghttp2_session_push_back_deferred_data(session);
|
||||
} else {
|
||||
return 0;
|
||||
rv = nghttp2_session_push_back_deferred_data(session);
|
||||
if(rv != 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return nghttp2_session_call_on_frame_received(session, frame);
|
||||
}
|
||||
|
||||
static int session_on_stream_window_update_received
|
||||
|
@ -2484,17 +2528,18 @@ static int get_error_code_from_lib_error_code(int lib_error_code)
|
|||
}
|
||||
}
|
||||
|
||||
/* For errors, this function only returns FATAL error. */
|
||||
/* For errors, this function returns NGHTTP2_ERR_PAUSE, or FATAL
|
||||
ones. */
|
||||
static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
|
||||
{
|
||||
int r = 0;
|
||||
uint16_t type;
|
||||
nghttp2_frame frame;
|
||||
nghttp2_frame *frame = &session->iframe.frame;
|
||||
type = session->iframe.headbuf[2];
|
||||
switch(type) {
|
||||
case NGHTTP2_HEADERS:
|
||||
if(session->iframe.error_code == 0) {
|
||||
r = nghttp2_frame_unpack_headers(&frame.headers,
|
||||
r = nghttp2_frame_unpack_headers(&frame->headers,
|
||||
session->iframe.headbuf,
|
||||
sizeof(session->iframe.headbuf),
|
||||
session->iframe.buf,
|
||||
|
@ -2502,7 +2547,7 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
|
|||
&session->hd_inflater);
|
||||
} else if(session->iframe.error_code == NGHTTP2_ERR_FRAME_TOO_LARGE) {
|
||||
r = nghttp2_frame_unpack_headers_without_nv
|
||||
(&frame.headers,
|
||||
(&frame->headers,
|
||||
session->iframe.headbuf, sizeof(session->iframe.headbuf),
|
||||
session->iframe.buf, session->iframe.buflen);
|
||||
if(r == 0) {
|
||||
|
@ -2513,74 +2558,82 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
|
|||
}
|
||||
if(r == 0) {
|
||||
nghttp2_stream *stream;
|
||||
stream = nghttp2_session_get_stream(session, frame.hd.stream_id);
|
||||
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(nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
|
||||
if(stream->state == NGHTTP2_STREAM_OPENING) {
|
||||
frame.headers.cat = NGHTTP2_HCAT_RESPONSE;
|
||||
frame->headers.cat = NGHTTP2_HCAT_RESPONSE;
|
||||
r = nghttp2_session_on_response_headers_received
|
||||
(session, &frame, stream);
|
||||
(session, frame, stream);
|
||||
} else {
|
||||
frame.headers.cat = NGHTTP2_HCAT_HEADERS;
|
||||
r = nghttp2_session_on_headers_received(session, &frame, stream);
|
||||
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_PUSH_RESPONSE;
|
||||
frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE;
|
||||
r = nghttp2_session_on_push_response_headers_received
|
||||
(session, &frame, stream);
|
||||
(session, frame, stream);
|
||||
} else {
|
||||
frame.headers.cat = NGHTTP2_HCAT_HEADERS;
|
||||
r = nghttp2_session_on_headers_received(session, &frame, stream);
|
||||
frame->headers.cat = NGHTTP2_HCAT_HEADERS;
|
||||
r = nghttp2_session_on_headers_received(session, frame, stream);
|
||||
}
|
||||
} else {
|
||||
frame.headers.cat = NGHTTP2_HCAT_REQUEST;
|
||||
r = nghttp2_session_on_request_headers_received(session, &frame);
|
||||
frame->headers.cat = NGHTTP2_HCAT_REQUEST;
|
||||
r = nghttp2_session_on_request_headers_received(session, frame);
|
||||
}
|
||||
if(r != NGHTTP2_ERR_PAUSE) {
|
||||
nghttp2_frame_headers_free(&frame->headers);
|
||||
nghttp2_hd_end_headers(&session->hd_inflater);
|
||||
}
|
||||
nghttp2_frame_headers_free(&frame.headers);
|
||||
nghttp2_hd_end_headers(&session->hd_inflater);
|
||||
} else if(nghttp2_is_non_fatal(r)) {
|
||||
r = nghttp2_session_handle_parse_error
|
||||
(session, type, r, get_error_code_from_lib_error_code(r));
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_PRIORITY:
|
||||
r = nghttp2_frame_unpack_priority(&frame.priority,
|
||||
r = nghttp2_frame_unpack_priority(&frame->priority,
|
||||
session->iframe.headbuf,
|
||||
sizeof(session->iframe.headbuf),
|
||||
session->iframe.buf,
|
||||
session->iframe.buflen);
|
||||
if(r == 0) {
|
||||
r = nghttp2_session_on_priority_received(session, &frame);
|
||||
nghttp2_frame_priority_free(&frame.priority);
|
||||
r = nghttp2_session_on_priority_received(session, frame);
|
||||
if(r != NGHTTP2_ERR_PAUSE) {
|
||||
nghttp2_frame_priority_free(&frame->priority);
|
||||
}
|
||||
} else if(nghttp2_is_non_fatal(r)) {
|
||||
r = nghttp2_session_handle_parse_error(session, type, r,
|
||||
NGHTTP2_PROTOCOL_ERROR);
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_RST_STREAM:
|
||||
r = nghttp2_frame_unpack_rst_stream(&frame.rst_stream,
|
||||
r = nghttp2_frame_unpack_rst_stream(&frame->rst_stream,
|
||||
session->iframe.headbuf,
|
||||
sizeof(session->iframe.headbuf),
|
||||
session->iframe.buf,
|
||||
session->iframe.buflen);
|
||||
if(r == 0) {
|
||||
r = nghttp2_session_on_rst_stream_received(session, &frame);
|
||||
nghttp2_frame_rst_stream_free(&frame.rst_stream);
|
||||
r = nghttp2_session_on_rst_stream_received(session, frame);
|
||||
if(r != NGHTTP2_ERR_PAUSE) {
|
||||
nghttp2_frame_rst_stream_free(&frame->rst_stream);
|
||||
}
|
||||
} else if(nghttp2_is_non_fatal(r)) {
|
||||
r = nghttp2_session_handle_parse_error(session, type, r,
|
||||
NGHTTP2_PROTOCOL_ERROR);
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_SETTINGS:
|
||||
r = nghttp2_frame_unpack_settings(&frame.settings,
|
||||
r = nghttp2_frame_unpack_settings(&frame->settings,
|
||||
session->iframe.headbuf,
|
||||
sizeof(session->iframe.headbuf),
|
||||
session->iframe.buf,
|
||||
session->iframe.buflen);
|
||||
if(r == 0) {
|
||||
r = nghttp2_session_on_settings_received(session, &frame);
|
||||
nghttp2_frame_settings_free(&frame.settings);
|
||||
r = nghttp2_session_on_settings_received(session, frame);
|
||||
if(r != NGHTTP2_ERR_PAUSE) {
|
||||
nghttp2_frame_settings_free(&frame->settings);
|
||||
}
|
||||
} else if(nghttp2_is_non_fatal(r)) {
|
||||
r = nghttp2_session_handle_parse_error(session, type, r,
|
||||
NGHTTP2_PROTOCOL_ERROR);
|
||||
|
@ -2588,7 +2641,7 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
|
|||
break;
|
||||
case NGHTTP2_PUSH_PROMISE:
|
||||
if(session->iframe.error_code == 0) {
|
||||
r = nghttp2_frame_unpack_push_promise(&frame.push_promise,
|
||||
r = nghttp2_frame_unpack_push_promise(&frame->push_promise,
|
||||
session->iframe.headbuf,
|
||||
sizeof(session->iframe.headbuf),
|
||||
session->iframe.buf,
|
||||
|
@ -2598,51 +2651,59 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
|
|||
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);
|
||||
r = nghttp2_session_on_push_promise_received(session, frame);
|
||||
if(r != NGHTTP2_ERR_PAUSE) {
|
||||
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, get_error_code_from_lib_error_code(r));
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_PING:
|
||||
r = nghttp2_frame_unpack_ping(&frame.ping,
|
||||
r = nghttp2_frame_unpack_ping(&frame->ping,
|
||||
session->iframe.headbuf,
|
||||
sizeof(session->iframe.headbuf),
|
||||
session->iframe.buf,
|
||||
session->iframe.buflen);
|
||||
if(r == 0) {
|
||||
r = nghttp2_session_on_ping_received(session, &frame);
|
||||
nghttp2_frame_ping_free(&frame.ping);
|
||||
r = nghttp2_session_on_ping_received(session, frame);
|
||||
if(r != NGHTTP2_ERR_PAUSE) {
|
||||
nghttp2_frame_ping_free(&frame->ping);
|
||||
}
|
||||
} else if(nghttp2_is_non_fatal(r)) {
|
||||
r = nghttp2_session_handle_parse_error(session, type, r,
|
||||
NGHTTP2_PROTOCOL_ERROR);
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_GOAWAY:
|
||||
r = nghttp2_frame_unpack_goaway(&frame.goaway,
|
||||
r = nghttp2_frame_unpack_goaway(&frame->goaway,
|
||||
session->iframe.headbuf,
|
||||
sizeof(session->iframe.headbuf),
|
||||
session->iframe.buf,
|
||||
session->iframe.buflen);
|
||||
if(r == 0) {
|
||||
r = nghttp2_session_on_goaway_received(session, &frame);
|
||||
nghttp2_frame_goaway_free(&frame.goaway);
|
||||
r = nghttp2_session_on_goaway_received(session, frame);
|
||||
if(r != NGHTTP2_ERR_PAUSE) {
|
||||
nghttp2_frame_goaway_free(&frame->goaway);
|
||||
}
|
||||
} else if(nghttp2_is_non_fatal(r)) {
|
||||
r = nghttp2_session_handle_parse_error(session, type, r,
|
||||
NGHTTP2_PROTOCOL_ERROR);
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_WINDOW_UPDATE:
|
||||
r = nghttp2_frame_unpack_window_update(&frame.window_update,
|
||||
r = nghttp2_frame_unpack_window_update(&frame->window_update,
|
||||
session->iframe.headbuf,
|
||||
sizeof(session->iframe.headbuf),
|
||||
session->iframe.buf,
|
||||
session->iframe.buflen);
|
||||
if(r == 0) {
|
||||
r = nghttp2_session_on_window_update_received(session, &frame);
|
||||
nghttp2_frame_window_update_free(&frame.window_update);
|
||||
r = nghttp2_session_on_window_update_received(session, frame);
|
||||
if(r != NGHTTP2_ERR_PAUSE) {
|
||||
nghttp2_frame_window_update_free(&frame->window_update);
|
||||
}
|
||||
} else if(nghttp2_is_non_fatal(r)) {
|
||||
r = nghttp2_session_handle_parse_error(session, type, r,
|
||||
NGHTTP2_PROTOCOL_ERROR);
|
||||
|
@ -2662,7 +2723,10 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
|
|||
}
|
||||
}
|
||||
}
|
||||
if(nghttp2_is_fatal(r)) {
|
||||
if(r == NGHTTP2_ERR_PAUSE) {
|
||||
session->iframe.error_code = NGHTTP2_ERR_PAUSE;
|
||||
return r;
|
||||
} else if(nghttp2_is_fatal(r)) {
|
||||
return r;
|
||||
} else {
|
||||
return 0;
|
||||
|
@ -2884,6 +2948,73 @@ static int nghttp2_session_check_data_recv_allowed(nghttp2_session *session,
|
|||
return 0;
|
||||
}
|
||||
|
||||
int nghttp2_session_continue(nghttp2_session *session)
|
||||
{
|
||||
nghttp2_frame *frame = &session->iframe.frame;
|
||||
nghttp2_stream *stream;
|
||||
int rv = 0;
|
||||
if(session->iframe.error_code != NGHTTP2_ERR_PAUSE) {
|
||||
return 0;
|
||||
}
|
||||
switch(frame->hd.type) {
|
||||
case NGHTTP2_DATA:
|
||||
/* To call on_data_recv_callback */
|
||||
return nghttp2_session_mem_recv(session, NULL, 0);
|
||||
case NGHTTP2_HEADERS:
|
||||
switch(frame->headers.cat) {
|
||||
case NGHTTP2_HCAT_REQUEST:
|
||||
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||
rv = nghttp2_session_call_on_request_recv(session, frame->hd.stream_id);
|
||||
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
|
||||
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_HCAT_RESPONSE:
|
||||
case NGHTTP2_HCAT_PUSH_RESPONSE:
|
||||
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
|
||||
/* This is the last frame of this stream, so disallow
|
||||
further receptions. */
|
||||
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||
rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
|
||||
if(!nghttp2_is_fatal(rv)) {
|
||||
rv = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_HCAT_HEADERS:
|
||||
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||
if(!nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
|
||||
rv = nghttp2_session_call_on_request_recv(session,
|
||||
frame->hd.stream_id);
|
||||
if(rv != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
|
||||
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||
rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
|
||||
if(!nghttp2_is_fatal(rv)) {
|
||||
rv = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_RST_STREAM:
|
||||
rv = nghttp2_session_close_stream(session, frame->hd.stream_id,
|
||||
frame->rst_stream.error_code);
|
||||
if(!nghttp2_is_fatal(rv)) {
|
||||
rv = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
nghttp2_inbound_frame_reset(session);
|
||||
return rv;
|
||||
}
|
||||
|
||||
ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
|
||||
const uint8_t *in, size_t inlen)
|
||||
{
|
||||
|
@ -2968,27 +3099,6 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
|
|||
memcpy(session->iframe.buf+session->iframe.off, inmark, readlen);
|
||||
}
|
||||
} else {
|
||||
/* For data frame, We don't buffer data. Instead, just pass
|
||||
received data to callback function. */
|
||||
data_stream_id = nghttp2_get_uint32(&session->iframe.headbuf[4]) &
|
||||
NGHTTP2_STREAM_ID_MASK;
|
||||
data_flags = session->iframe.headbuf[3];
|
||||
if(session->iframe.state != NGHTTP2_RECV_PAYLOAD_IGN) {
|
||||
if(session->callbacks.on_data_chunk_recv_callback) {
|
||||
if(session->callbacks.on_data_chunk_recv_callback
|
||||
(session,
|
||||
data_flags,
|
||||
data_stream_id,
|
||||
inmark,
|
||||
readlen,
|
||||
session->user_data) != 0) {
|
||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* TODO We need on_ignored_data_chunk_recv_callback, for
|
||||
ingored DATA frame, so that the application can calculate
|
||||
connection-level window size. */
|
||||
}
|
||||
session->iframe.off += readlen;
|
||||
inmark += readlen;
|
||||
|
@ -3020,6 +3130,35 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
|
|||
}
|
||||
}
|
||||
}
|
||||
/* For data frame, We don't buffer data. Instead, just pass
|
||||
received data to callback function. */
|
||||
data_stream_id = nghttp2_get_uint32(&session->iframe.headbuf[4]) &
|
||||
NGHTTP2_STREAM_ID_MASK;
|
||||
data_flags = session->iframe.headbuf[3];
|
||||
if(session->iframe.state != NGHTTP2_RECV_PAYLOAD_IGN) {
|
||||
if(session->callbacks.on_data_chunk_recv_callback) {
|
||||
r = session->callbacks.on_data_chunk_recv_callback
|
||||
(session,
|
||||
data_flags,
|
||||
data_stream_id,
|
||||
inmark - readlen,
|
||||
readlen,
|
||||
session->user_data);
|
||||
if(r == NGHTTP2_ERR_PAUSE) {
|
||||
/* Set type to NGHTTP2_DATA, so that we can see what was
|
||||
paused in nghttp2_session_continue() */
|
||||
session->iframe.error_code = NGHTTP2_ERR_PAUSE;
|
||||
session->iframe.frame.hd.type = NGHTTP2_DATA;
|
||||
return inmark - in;
|
||||
}
|
||||
if(r != 0) {
|
||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* TODO We need on_ignored_data_chunk_recv_callback, for
|
||||
ingored DATA frame, so that the application can calculate
|
||||
connection-level window size. */
|
||||
}
|
||||
if(session->iframe.payloadlen == session->iframe.off) {
|
||||
if(session->iframe.error_code != NGHTTP2_ERR_FRAME_TOO_LARGE) {
|
||||
|
@ -3030,13 +3169,16 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
|
|||
} else {
|
||||
r = nghttp2_session_process_data_frame(session);
|
||||
}
|
||||
if(r == NGHTTP2_ERR_PAUSE) {
|
||||
return inmark - in;
|
||||
}
|
||||
if(r < 0) {
|
||||
/* FATAL */
|
||||
assert(r < NGHTTP2_ERR_FATAL);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
nghttp2_inbound_frame_reset(&session->iframe);
|
||||
nghttp2_inbound_frame_reset(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,6 +80,7 @@ typedef enum {
|
|||
} nghttp2_inbound_state;
|
||||
|
||||
typedef struct {
|
||||
nghttp2_frame frame;
|
||||
nghttp2_inbound_state state;
|
||||
uint8_t headbuf[NGHTTP2_FRAME_HEAD_LENGTH];
|
||||
/* How many bytes are filled in headbuf */
|
||||
|
|
|
@ -86,6 +86,7 @@ int main(int argc, char* argv[])
|
|||
test_nghttp2_session_recv_data) ||
|
||||
!CU_add_test(pSuite, "session_recv_frame_too_large",
|
||||
test_nghttp2_session_recv_frame_too_large) ||
|
||||
!CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) ||
|
||||
!CU_add_test(pSuite, "session_add_frame",
|
||||
test_nghttp2_session_add_frame) ||
|
||||
!CU_add_test(pSuite, "session_on_request_headers_received",
|
||||
|
|
|
@ -67,6 +67,7 @@ typedef struct {
|
|||
size_t block_count;
|
||||
int data_chunk_recv_cb_called;
|
||||
int data_recv_cb_called;
|
||||
const nghttp2_frame *frame;
|
||||
} my_user_data;
|
||||
|
||||
static void scripted_data_feed_init(scripted_data_feed *df,
|
||||
|
@ -135,6 +136,16 @@ static int on_frame_recv_callback(nghttp2_session *session,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int pause_on_frame_recv_callback(nghttp2_session *session,
|
||||
const nghttp2_frame *frame,
|
||||
void *user_data)
|
||||
{
|
||||
my_user_data *ud = (my_user_data*)user_data;
|
||||
++ud->frame_recv_cb_called;
|
||||
ud->frame = frame;
|
||||
return NGHTTP2_ERR_PAUSE;
|
||||
}
|
||||
|
||||
static int on_invalid_frame_recv_callback(nghttp2_session *session,
|
||||
const nghttp2_frame *frame,
|
||||
nghttp2_error_code error_code,
|
||||
|
@ -177,6 +188,16 @@ static int on_data_chunk_recv_callback(nghttp2_session *session,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int pause_on_data_chunk_recv_callback(nghttp2_session *session,
|
||||
uint8_t flags, int32_t stream_id,
|
||||
const uint8_t *data, size_t len,
|
||||
void *user_data)
|
||||
{
|
||||
my_user_data *ud = (my_user_data*)user_data;
|
||||
++ud->data_chunk_recv_cb_called;
|
||||
return NGHTTP2_ERR_PAUSE;
|
||||
}
|
||||
|
||||
static int on_data_recv_callback(nghttp2_session *session,
|
||||
uint16_t length, uint8_t flags,
|
||||
int32_t stream_id, void *user_data)
|
||||
|
@ -631,6 +652,117 @@ void test_nghttp2_session_recv_data(void)
|
|||
nghttp2_session_del(session);
|
||||
}
|
||||
|
||||
void test_nghttp2_session_continue(void)
|
||||
{
|
||||
nghttp2_session *session;
|
||||
nghttp2_session_callbacks callbacks;
|
||||
my_user_data user_data;
|
||||
const char *nv1[] = {
|
||||
"url", "/", NULL
|
||||
};
|
||||
const char *nv2[] = {
|
||||
"user-agent", "nghttp2/1.0.0", NULL
|
||||
};
|
||||
uint8_t *framedata = NULL;
|
||||
size_t framedatalen = 0;
|
||||
ssize_t framelen1, framelen2;
|
||||
ssize_t rv;
|
||||
uint8_t buffer[4096];
|
||||
size_t buflen;
|
||||
nghttp2_frame frame;
|
||||
nghttp2_nv *nva;
|
||||
ssize_t nvlen;
|
||||
const nghttp2_frame *recv_frame;
|
||||
nghttp2_nv nvcheck;
|
||||
nghttp2_frame_hd data_hd;
|
||||
|
||||
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
|
||||
callbacks.send_callback = null_send_callback;
|
||||
callbacks.on_frame_recv_callback = pause_on_frame_recv_callback;
|
||||
callbacks.on_data_chunk_recv_callback = pause_on_data_chunk_recv_callback;
|
||||
callbacks.on_data_recv_callback = on_data_recv_callback;
|
||||
|
||||
nghttp2_session_server_new(&session, &callbacks, &user_data);
|
||||
|
||||
/* Make 2 HEADERS frames */
|
||||
nvlen = nghttp2_nv_array_from_cstr(&nva, nv1);
|
||||
nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS,
|
||||
1, NGHTTP2_PRI_DEFAULT, nva, nvlen);
|
||||
framelen1 = nghttp2_frame_pack_headers(&framedata, &framedatalen,
|
||||
&frame.headers,
|
||||
&session->hd_deflater);
|
||||
nghttp2_frame_headers_free(&frame.headers);
|
||||
|
||||
nghttp2_hd_end_headers(&session->hd_deflater);
|
||||
memcpy(buffer, framedata, framelen1);
|
||||
|
||||
nvlen = nghttp2_nv_array_from_cstr(&nva, nv2);
|
||||
nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS,
|
||||
3, NGHTTP2_PRI_DEFAULT, nva, nvlen);
|
||||
framelen2 = nghttp2_frame_pack_headers(&framedata, &framedatalen,
|
||||
&frame.headers,
|
||||
&session->hd_deflater);
|
||||
nghttp2_frame_headers_free(&frame.headers);
|
||||
|
||||
nghttp2_hd_end_headers(&session->hd_deflater);
|
||||
memcpy(buffer + framelen1, framedata, framelen2);
|
||||
buflen = framelen1 + framelen2;
|
||||
|
||||
/* Receive 1st HEADERS and pause */
|
||||
rv = nghttp2_session_mem_recv(session, buffer, buflen);
|
||||
CU_ASSERT(rv == framelen1);
|
||||
|
||||
recv_frame = user_data.frame;
|
||||
CU_ASSERT(NGHTTP2_HEADERS == recv_frame->hd.type);
|
||||
CU_ASSERT(framelen1 - NGHTTP2_FRAME_HEAD_LENGTH == recv_frame->hd.length);
|
||||
CU_ASSERT(1 == recv_frame->headers.nvlen);
|
||||
nvcheck.name = (uint8_t*)nv1[0];
|
||||
nvcheck.namelen = strlen(nv1[0]);
|
||||
nvcheck.value = (uint8_t*)nv1[1];
|
||||
nvcheck.valuelen = strlen(nv1[1]);
|
||||
CU_ASSERT(nghttp2_nv_equal(&nvcheck, recv_frame->headers.nva));
|
||||
|
||||
rv = nghttp2_session_continue(session);
|
||||
CU_ASSERT(rv == 0);
|
||||
|
||||
/* Receive 2nd HEADERS and pause */
|
||||
rv = nghttp2_session_mem_recv(session, buffer + framelen1,
|
||||
buflen - framelen1);
|
||||
CU_ASSERT(rv == framelen2);
|
||||
|
||||
recv_frame = user_data.frame;
|
||||
CU_ASSERT(NGHTTP2_HEADERS == recv_frame->hd.type);
|
||||
CU_ASSERT(framelen2 - NGHTTP2_FRAME_HEAD_LENGTH == recv_frame->hd.length);
|
||||
CU_ASSERT(1 == recv_frame->headers.nvlen);
|
||||
nvcheck.name = (uint8_t*)nv2[0];
|
||||
nvcheck.namelen = strlen(nv2[0]);
|
||||
nvcheck.value = (uint8_t*)nv2[1];
|
||||
nvcheck.valuelen = strlen(nv2[1]);
|
||||
CU_ASSERT(nghttp2_nv_equal(&nvcheck, recv_frame->headers.nva));
|
||||
|
||||
rv = nghttp2_session_continue(session);
|
||||
CU_ASSERT(rv == 0);
|
||||
|
||||
/* Receive DATA */
|
||||
data_hd.length = 16;
|
||||
data_hd.type = NGHTTP2_DATA;
|
||||
data_hd.flags = NGHTTP2_FLAG_NONE;
|
||||
data_hd.stream_id = 1;
|
||||
nghttp2_frame_pack_frame_hd(buffer, &data_hd);
|
||||
/* Intentionally specify larger buffer size to see pause is kicked
|
||||
in. */
|
||||
rv = nghttp2_session_mem_recv(session, buffer, sizeof(buffer));
|
||||
CU_ASSERT(16 + NGHTTP2_FRAME_HEAD_LENGTH == rv);
|
||||
|
||||
user_data.data_recv_cb_called = 0;
|
||||
rv = nghttp2_session_continue(session);
|
||||
CU_ASSERT(rv == 0);
|
||||
CU_ASSERT(1 == user_data.data_recv_cb_called);
|
||||
|
||||
free(framedata);
|
||||
nghttp2_session_del(session);
|
||||
}
|
||||
|
||||
void test_nghttp2_session_add_frame(void)
|
||||
{
|
||||
nghttp2_session *session;
|
||||
|
|
|
@ -31,6 +31,7 @@ void test_nghttp2_session_recv_invalid_frame(void);
|
|||
void test_nghttp2_session_recv_eof(void);
|
||||
void test_nghttp2_session_recv_data(void);
|
||||
void test_nghttp2_session_recv_frame_too_large(void);
|
||||
void test_nghttp2_session_continue(void);
|
||||
void test_nghttp2_session_add_frame(void);
|
||||
void test_nghttp2_session_on_request_headers_received(void);
|
||||
void test_nghttp2_session_on_response_headers_received(void);
|
||||
|
|
Loading…
Reference in New Issue