Commit 21d76dcc authored by Tatsuhiro Tsujikawa's avatar Tatsuhiro Tsujikawa

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.
parent bfe7a9af
......@@ -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
*
......
This diff is collapsed.
......@@ -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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment