diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index ad21715b..3e54be5b 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -1163,6 +1163,9 @@ typedef ssize_t (*nghttp2_recv_callback) * If ``frame->hd.flags & NGHTTP2_FLAG_END_STREAM`` is nonzero, the * |frame| is the last frame from the remote peer in this stream. * + * This callback won't be called for CONTINUATION frames. + * HEADERS/PUSH_PROMISE + CONTINUATIONs are treated as single frame. + * * The implementation of this function must return 0 if it succeeds. * If nonzero value is returned, it is treated as fatal error and * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions @@ -1440,6 +1443,31 @@ typedef ssize_t (*nghttp2_select_padding_callback) size_t max_payloadlen, void *user_data); +/** + * @functypedef + * + * Callback function invoked when a frame header is received. The + * |hd| points to received frame header. + * + * Unlike :type:`nghttp2_on_frame_recv_callback`, this callback will + * also be called when frame header of CONTINUATION frame is received. + * + * If both :type:`nghttp2_on_begin_frame_callback` and + * :type:`nghttp2_on_begin_headers_callback` are set and HEADERS or + * PUSH_PROMISE is received, :type:`nghttp2_on_begin_frame_callback` + * will be called first. + * + * The implementation of this function must return 0 if it succeeds. + * If nonzero value is returned, it is treated as fatal error and + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions + * immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_begin_frame_callback()`. + */ +typedef int (*nghttp2_on_begin_frame_callback) +(nghttp2_session *session, const nghttp2_frame_hd *hd, void *user_data); + struct nghttp2_session_callbacks; /** @@ -1608,6 +1636,15 @@ void nghttp2_session_callbacks_set_data_source_read_length_callback (nghttp2_session_callbacks *cbs, nghttp2_data_source_read_length_callback data_source_read_length_callback); +/** + * @function + * + * Sets callback function invoked when a frame header is received. + */ +void nghttp2_session_callbacks_set_on_begin_frame_callback +(nghttp2_session_callbacks *cbs, + nghttp2_on_begin_frame_callback on_begin_frame_callback); + struct nghttp2_option; /** @@ -1879,7 +1916,10 @@ ssize_t nghttp2_session_mem_send(nghttp2_session *session, * 1. :type:`nghttp2_recv_callback` is invoked one or more times to * receive frame header. * - * 2. If the frame is DATA frame: + * 2. When frame header is received, + * :type:`nghttp2_on_begin_frame_callback` is invoked. + * + * 3. If the frame is DATA frame: * * 1. :type:`nghttp2_recv_callback` is invoked to receive DATA * payload. For each chunk of data, @@ -1890,7 +1930,7 @@ ssize_t nghttp2_session_mem_send(nghttp2_session *session, * reception of the frame triggers the closure of the stream, * :type:`nghttp2_on_stream_close_callback` is invoked. * - * 3. If the frame is the control frame: + * 4. If the frame is the control frame: * * 1. :type:`nghttp2_recv_callback` is invoked one or more times to * receive whole frame. diff --git a/lib/nghttp2_callbacks.c b/lib/nghttp2_callbacks.c index 9a3aa4d9..4b15bfca 100644 --- a/lib/nghttp2_callbacks.c +++ b/lib/nghttp2_callbacks.c @@ -130,3 +130,10 @@ void nghttp2_session_callbacks_set_data_source_read_length_callback { cbs->read_length_callback = data_source_read_length_callback; } + +void nghttp2_session_callbacks_set_on_begin_frame_callback +(nghttp2_session_callbacks *cbs, + nghttp2_on_begin_frame_callback on_begin_frame_callback) +{ + cbs->on_begin_frame_callback = on_begin_frame_callback; +} diff --git a/lib/nghttp2_callbacks.h b/lib/nghttp2_callbacks.h index 5a4792c4..e1287ca1 100644 --- a/lib/nghttp2_callbacks.h +++ b/lib/nghttp2_callbacks.h @@ -102,6 +102,10 @@ struct nghttp2_session_callbacks { * `nghttp2_data_source_read_callback()` */ nghttp2_data_source_read_length_callback read_length_callback; + /** + * Sets callback function invoked when a frame header is received. + */ + nghttp2_on_begin_frame_callback on_begin_frame_callback; }; #endif /* NGHTTP2_CALLBACKS_H */ diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 835cb00d..20fe0a0a 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -2453,6 +2453,24 @@ static ssize_t session_recv(nghttp2_session *session, uint8_t *buf, size_t len) return rv; } +static int session_call_on_begin_frame +(nghttp2_session *session, const nghttp2_frame_hd *hd) +{ + int rv; + + if(session->callbacks.on_begin_frame_callback) { + + rv = session->callbacks.on_begin_frame_callback + (session, hd, session->user_data); + + if(rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + + return 0; +} + static int session_call_on_frame_received (nghttp2_session *session, nghttp2_frame *frame) { @@ -4295,7 +4313,9 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, for(;;) { switch(iframe->state) { - case NGHTTP2_IB_READ_HEAD: + case NGHTTP2_IB_READ_HEAD: { + int on_begin_frame_called = 0; + DEBUGF(fprintf(stderr, "recv: [IB_READ_HEAD]\n")); readlen = inbound_frame_buf_read(iframe, in, last); @@ -4424,6 +4444,17 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, break; } + /* Call on_begin_frame_callback here because + session_process_headers_frame() may call + on_begin_headers_callback */ + rv = session_call_on_begin_frame(session, &iframe->frame.hd); + + if(nghttp2_is_fatal(rv)) { + return rv; + } + + on_begin_frame_called = 1; + rv = session_process_headers_frame(session); if(nghttp2_is_fatal(rv)) { return rv; @@ -4632,7 +4663,25 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, break; } + + if(!on_begin_frame_called) { + switch(iframe->state) { + case NGHTTP2_IB_IGN_HEADER_BLOCK: + case NGHTTP2_IB_IGN_PAYLOAD: + case NGHTTP2_IB_FRAME_SIZE_ERROR: + case NGHTTP2_IB_IGN_DATA: + break; + default: + rv = session_call_on_begin_frame(session, &iframe->frame.hd); + + if(nghttp2_is_fatal(rv)) { + return rv; + } + } + } + break; + } case NGHTTP2_IB_READ_NBYTE: DEBUGF(fprintf(stderr, "recv: [IB_READ_NBYTE]\n")); @@ -5105,6 +5154,12 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, if(iframe->state == NGHTTP2_IB_EXPECT_CONTINUATION) { iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK; + + rv = session_call_on_begin_frame(session, &cont_hd); + + if(nghttp2_is_fatal(rv)) { + return rv; + } } else { iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; } diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 2a17fce2..c256bf12 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -75,6 +75,7 @@ typedef struct { nghttp2_nv nv; size_t data_chunk_len; size_t padlen; + int begin_frame_cb_called; } my_user_data; static void scripted_data_feed_init2(scripted_data_feed *df, @@ -156,6 +157,15 @@ static ssize_t accumulator_send_callback(nghttp2_session *session, return len; } +static int on_begin_frame_callback(nghttp2_session *session, + const nghttp2_frame_hd *hd, + void *user_data) +{ + my_user_data *ud = (my_user_data*)user_data; + ++ud->begin_frame_cb_called; + return 0; +} + static int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) @@ -409,6 +419,7 @@ void test_nghttp2_session_recv(void) callbacks.send_callback = null_send_callback; callbacks.recv_callback = scripted_recv_callback; callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_begin_frame_callback = on_begin_frame_callback; user_data.df = &df; @@ -435,10 +446,13 @@ void test_nghttp2_session_recv(void) nghttp2_frame_headers_free(&frame.headers); user_data.frame_recv_cb_called = 0; + user_data.begin_frame_cb_called = 0; + while((ssize_t)df.seqidx < framelen) { CU_ASSERT(0 == nghttp2_session_recv(session)); } CU_ASSERT(1 == user_data.frame_recv_cb_called); + CU_ASSERT(1 == user_data.begin_frame_cb_called); nghttp2_bufs_reset(&bufs); @@ -453,9 +467,33 @@ void test_nghttp2_session_recv(void) scripted_data_feed_init2(&df, &bufs); user_data.frame_recv_cb_called = 0; + user_data.begin_frame_cb_called = 0; CU_ASSERT(0 == nghttp2_session_recv(session)); CU_ASSERT(1 == user_data.frame_recv_cb_called); + CU_ASSERT(1 == user_data.begin_frame_cb_called); + + nghttp2_bufs_reset(&bufs); + + /* Receive PRIORITY */ + nghttp2_frame_priority_init(&frame.priority, 5, &pri_spec_default); + + rv = nghttp2_frame_pack_priority(&bufs, &frame.priority); + + CU_ASSERT(0 == rv); + + nghttp2_frame_priority_free(&frame.priority); + + scripted_data_feed_init2(&df, &bufs); + + user_data.frame_recv_cb_called = 0; + user_data.begin_frame_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_recv(session)); + CU_ASSERT(1 == user_data.frame_recv_cb_called); + CU_ASSERT(1 == user_data.begin_frame_cb_called); + + nghttp2_bufs_reset(&bufs); nghttp2_hd_deflate_free(&deflater); nghttp2_session_del(session); @@ -463,8 +501,6 @@ void test_nghttp2_session_recv(void) /* Some tests for frame too large */ nghttp2_session_server_new(&session, &callbacks, &user_data); - nghttp2_bufs_reset(&bufs); - /* Receive PING with too large payload */ nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL); @@ -485,8 +521,12 @@ void test_nghttp2_session_recv(void) scripted_data_feed_init2(&df, &bufs); user_data.frame_recv_cb_called = 0; + user_data.begin_frame_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_recv(session)); CU_ASSERT(0 == user_data.frame_recv_cb_called); + CU_ASSERT(0 == user_data.begin_frame_cb_called); + item = nghttp2_session_get_next_ob_item(session); CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item)); CU_ASSERT(NGHTTP2_FRAME_SIZE_ERROR == OB_CTRL(item)->goaway.error_code); @@ -760,6 +800,7 @@ void test_nghttp2_session_recv_continuation(void) memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); callbacks.on_header_callback = on_header_callback; callbacks.on_begin_headers_callback = on_begin_headers_callback; + callbacks.on_begin_frame_callback = on_begin_frame_callback; nghttp2_session_server_new(&session, &callbacks, &ud); @@ -816,9 +857,12 @@ void test_nghttp2_session_recv_continuation(void) CU_ASSERT(0 == nghttp2_buf_len(buf)); ud.header_cb_called = 0; + ud.begin_frame_cb_called = 0; + rv = nghttp2_session_mem_recv(session, data, datalen); CU_ASSERT((ssize_t)datalen == rv); CU_ASSERT(2 == ud.header_cb_called); + CU_ASSERT(3 == ud.begin_frame_cb_called); nghttp2_hd_deflate_free(&deflater); nghttp2_session_del(session);