From 3785cf07ba654d3f1b02a538d87b621b78fb65e4 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 10 Oct 2015 22:27:48 +0900 Subject: [PATCH 01/13] Add simple HTTP/2 extension framework Application can utilize this framework to send/receive user defined extension frames. These frames are expected not to change existing protocol behaviour. --- doc/Makefile.am | 4 + lib/includes/nghttp2/nghttp2.h | 154 +++++++++++++++++++++++++++++++ lib/nghttp2_callbacks.c | 18 ++++ lib/nghttp2_callbacks.h | 3 + lib/nghttp2_frame.c | 9 ++ lib/nghttp2_frame.h | 6 ++ lib/nghttp2_helper.c | 2 + lib/nghttp2_outbound_item.c | 3 + lib/nghttp2_session.c | 159 ++++++++++++++++++++++++++++++++- lib/nghttp2_session.h | 3 +- lib/nghttp2_submit.c | 37 ++++++++ src/app_helper.cc | 12 ++- 12 files changed, 402 insertions(+), 8 deletions(-) diff --git a/doc/Makefile.am b/doc/Makefile.am index ca094fb0..d1e41e4a 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -62,16 +62,19 @@ APIDOCS= \ nghttp2_session_callbacks_set_on_begin_frame_callback.rst \ nghttp2_session_callbacks_set_on_begin_headers_callback.rst \ nghttp2_session_callbacks_set_on_data_chunk_recv_callback.rst \ + nghttp2_session_callbacks_set_on_extension_chunk_recv_callback.rst \ nghttp2_session_callbacks_set_on_frame_not_send_callback.rst \ nghttp2_session_callbacks_set_on_frame_recv_callback.rst \ nghttp2_session_callbacks_set_on_frame_send_callback.rst \ nghttp2_session_callbacks_set_on_header_callback.rst \ nghttp2_session_callbacks_set_on_invalid_frame_recv_callback.rst \ nghttp2_session_callbacks_set_on_stream_close_callback.rst \ + nghttp2_session_callbacks_set_pack_extension_callback.rst \ nghttp2_session_callbacks_set_recv_callback.rst \ nghttp2_session_callbacks_set_select_padding_callback.rst \ nghttp2_session_callbacks_set_send_callback.rst \ nghttp2_session_callbacks_set_send_data_callback.rst \ + nghttp2_session_callbacks_set_unpack_extension_callback.rst \ nghttp2_session_client_new.rst \ nghttp2_session_client_new2.rst \ nghttp2_session_client_new3.rst \ @@ -118,6 +121,7 @@ APIDOCS= \ nghttp2_stream_get_weight.rst \ nghttp2_strerror.rst \ nghttp2_submit_data.rst \ + nghttp2_submit_extension.rst \ nghttp2_submit_goaway.rst \ nghttp2_submit_headers.rst \ nghttp2_submit_ping.rst \ diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 037793ed..91030f28 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -378,6 +378,10 @@ typedef enum { * Unexpected internal error, but recovered. */ NGHTTP2_ERR_INTERNAL = -534, + /** + * Indicates that a processing was canceled. + */ + NGHTTP2_ERR_CANCEL = -535, /** * The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is * under unexpected condition and processing was terminated (e.g., @@ -1657,6 +1661,94 @@ typedef int (*nghttp2_on_begin_frame_callback)(nghttp2_session *session, const nghttp2_frame_hd *hd, void *user_data); +/** + * @functypedef + * + * Callback function invoked when chunk of extension frame payload is + * received. The |hd| points to frame header. The received + * chunk is |data| of length |len|. + * + * The implementation of this function must return 0 if it succeeds. + * + * To abort processing this extension frame, return + * :enum:`NGHTTP2_ERR_CANCEL`. + * + * If fatal error occurred, application should return + * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. In this case, + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions + * immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. If the + * other values are returned, currently they are treated as + * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp2_on_extension_chunk_recv_callback)( + nghttp2_session *session, const nghttp2_frame_hd *hd, const uint8_t *data, + size_t len, void *user_data); + +/** + * @functypedef + * + * Callback function invoked when library asks the application to + * unpack extension payload from its wire format. The extension + * payload has been passed to the application using + * :type:`nghttp2_on_extension_chunk_recv_callback`. The frame header + * is already unpacked by the library and provided as |hd|. + * + * The implementation of this function may store the pointer to the + * created object as a result of unpacking in |*payload|, and returns + * 0. The pointer stored in |*payload| is opaque to the library, and + * the library does not own its pointer. |*payload| is initialled as + * `NULL`. + * + * To abort processing this extension frame, return + * :enum:`NGHTTP2_ERR_CANCEL`. + * + * If fatal error occurred, application should return + * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. In this case, + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions + * immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. If the + * other values are returned, currently they are treated as + * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp2_unpack_extension_callback)(nghttp2_session *session, + void **payload, + const nghttp2_frame_hd *hd, + void *user_data); + +/** + * @functypedef + * + * Callbck function invoked when library asks the application to pack + * extension payload in its wire format. The frame header will be + * packed by library. Application must pack payload only. + * frame->ext.payload is the object passed to + * `nghttp2_submit_extension()` as payload parameter. The |*flags| is + * initialized to the flags parameter passed in + * `nghttp2_submit_extension()`. Application can modify flags value + * if it wants. Application must pack extension payload to the |buf| + * of its capacity |len| bytes. + * + * The implementation of this function returns the number of bytes + * written into |buf| when it succeeds. + * + * To abort processing this extension frame, return + * :enum:`NGHTTP2_ERR_CANCEL`, and + * :type:`nghttp2_on_frame_not_send_callback` will be invoked. + * + * If fatal error occurred, application should return + * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. In this case, + * `nghttp2_session_send()` and `nghttp2_session_mem_send()` functions + * immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. If the + * other values are returned, currently they are treated as + * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. If the return value is + * strictly larger than |len|, it is treated as + * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. + */ +typedef ssize_t (*nghttp2_pack_extension_callback)(nghttp2_session *session, + uint8_t *flags, uint8_t *buf, + size_t len, + const nghttp2_frame *frame, + void *user_data); + struct nghttp2_session_callbacks; /** @@ -1849,6 +1941,37 @@ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_send_data_callback( nghttp2_session_callbacks *cbs, nghttp2_send_data_callback send_data_callback); +/** + * @function + * + * Sets callback function invoked when the library asks the + * application to pack extension frame payload in wire format. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_pack_extension_callback( + nghttp2_session_callbacks *cbs, + nghttp2_pack_extension_callback pack_extension_callback); + +/** + * @function + * + * Sets callback function invoked when the library asks the + * application to unpack extension frame payload from wire format. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_unpack_extension_callback( + nghttp2_session_callbacks *cbs, + nghttp2_unpack_extension_callback unpack_extension_callback); + +/** + * @function + * + * Sets callback function invoked when chunk of extension frame + * payload is received. + */ +NGHTTP2_EXTERN void +nghttp2_session_callbacks_set_on_extension_chunk_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback); + /** * @functypedef * @@ -3515,6 +3638,37 @@ NGHTTP2_EXTERN int nghttp2_submit_window_update(nghttp2_session *session, int32_t stream_id, int32_t window_size_increment); +/** + * @function + * + * Submits extension frame. + * + * Application can pass arbitrary frame flags and stream ID to |flags| + * and |stream_id| respectively. The |payload| is opaque pointer, and + * it can be accessible though frame->ext.payload in + * :type:`nghttp2_pack_extension_callback`. The library will not own + * passed |payload| pointer. + * + * The standard HTTP/2 frame cannot be sent with this function, so + * |type| must be strictly grater than 0x9. Otherwise, this function + * will fail with error code :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`NGHTTP2_ERR_INVALID_STATE` + * If :type:`nghttp2_pack_extension_callback` is not set. + * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` + * If |type| specifies standard HTTP/2 frame type. The frame + * types in the rage [0x0, 0x9], both inclusive, are standard + * HTTP/2 frame type, and cannot be sent using this function. + * :enum:`NGHTTP2_ERR_NOMEM` + * Out of memory + */ +NGHTTP2_EXTERN int nghttp2_submit_extension(nghttp2_session *session, + uint8_t type, uint8_t flags, + int32_t stream_id, void *payload); + /** * @function * diff --git a/lib/nghttp2_callbacks.c b/lib/nghttp2_callbacks.c index 3c4be9a0..3b0369c1 100644 --- a/lib/nghttp2_callbacks.c +++ b/lib/nghttp2_callbacks.c @@ -127,3 +127,21 @@ void nghttp2_session_callbacks_set_send_data_callback( nghttp2_send_data_callback send_data_callback) { cbs->send_data_callback = send_data_callback; } + +void nghttp2_session_callbacks_set_pack_extension_callback( + nghttp2_session_callbacks *cbs, + nghttp2_pack_extension_callback pack_extension_callback) { + cbs->pack_extension_callback = pack_extension_callback; +} + +void nghttp2_session_callbacks_set_unpack_extension_callback( + nghttp2_session_callbacks *cbs, + nghttp2_unpack_extension_callback unpack_extension_callback) { + cbs->unpack_extension_callback = unpack_extension_callback; +} + +void nghttp2_session_callbacks_set_on_extension_chunk_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback) { + cbs->on_extension_chunk_recv_callback = on_extension_chunk_recv_callback; +} diff --git a/lib/nghttp2_callbacks.h b/lib/nghttp2_callbacks.h index 37958ea9..664bf35b 100644 --- a/lib/nghttp2_callbacks.h +++ b/lib/nghttp2_callbacks.h @@ -107,6 +107,9 @@ struct nghttp2_session_callbacks { */ nghttp2_on_begin_frame_callback on_begin_frame_callback; nghttp2_send_data_callback send_data_callback; + nghttp2_pack_extension_callback pack_extension_callback; + nghttp2_unpack_extension_callback unpack_extension_callback; + nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback; }; #endif /* NGHTTP2_CALLBACKS_H */ diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index e324b9c9..5677cea4 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -184,6 +184,15 @@ void nghttp2_frame_data_init(nghttp2_data *frame, uint8_t flags, void nghttp2_frame_data_free(nghttp2_data *frame _U_) {} +void nghttp2_frame_extension_init(nghttp2_extension *frame, uint8_t type, + uint8_t flags, int32_t stream_id, + void *payload) { + nghttp2_frame_hd_init(&frame->hd, 0, type, flags, stream_id); + frame->payload = payload; +} + +void nghttp2_frame_extension_free(nghttp2_extension *frame _U_) {} + size_t nghttp2_frame_priority_len(uint8_t flags) { if (flags & NGHTTP2_FLAG_PRIORITY) { return NGHTTP2_PRIORITY_SPECLEN; diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h index b0b02ee1..fa0eb452 100644 --- a/lib/nghttp2_frame.h +++ b/lib/nghttp2_frame.h @@ -439,6 +439,12 @@ void nghttp2_frame_window_update_init(nghttp2_window_update *frame, void nghttp2_frame_window_update_free(nghttp2_window_update *frame); +void nghttp2_frame_extension_init(nghttp2_extension *frame, uint8_t type, + uint8_t flags, int32_t stream_id, + void *payload); + +void nghttp2_frame_extension_free(nghttp2_extension *frame); + /* * Returns the number of padding bytes after payload. The total * padding length is given in the |padlen|. The returned value does diff --git a/lib/nghttp2_helper.c b/lib/nghttp2_helper.c index 884abc68..bdba797c 100644 --- a/lib/nghttp2_helper.c +++ b/lib/nghttp2_helper.c @@ -288,6 +288,8 @@ const char *nghttp2_strerror(int error_code) { return "Stream was refused"; case NGHTTP2_ERR_INTERNAL: return "Internal error"; + case NGHTTP2_ERR_CANCEL: + return "Cancel"; case NGHTTP2_ERR_NOMEM: return "Out of memory"; case NGHTTP2_ERR_CALLBACK_FAILURE: diff --git a/lib/nghttp2_outbound_item.c b/lib/nghttp2_outbound_item.c index c4ecab90..886f330c 100644 --- a/lib/nghttp2_outbound_item.c +++ b/lib/nghttp2_outbound_item.c @@ -72,6 +72,9 @@ void nghttp2_outbound_item_free(nghttp2_outbound_item *item, nghttp2_mem *mem) { case NGHTTP2_WINDOW_UPDATE: nghttp2_frame_window_update_free(&frame->window_update); break; + default: + nghttp2_frame_extension_free(&frame->ext); + break; } } diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 7712ac86..787bc311 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -1718,6 +1718,44 @@ static size_t session_estimate_headers_payload(nghttp2_session *session, additional; } +static int session_pack_extension(nghttp2_session *session, nghttp2_bufs *bufs, + nghttp2_frame *frame) { + ssize_t rv; + uint8_t flags; + nghttp2_buf *buf; + size_t buflen; + size_t framelen; + + assert(session->callbacks.pack_extension_callback); + + flags = frame->hd.flags; + buf = &bufs->head->buf; + buflen = nghttp2_min(nghttp2_buf_avail(buf), NGHTTP2_MAX_PAYLOADLEN); + + rv = session->callbacks.pack_extension_callback( + session, &flags, buf->last, buflen, frame, session->user_data); + if (rv == NGHTTP2_ERR_CANCEL) { + return (int)rv; + } + + if (rv < 0 || (size_t)rv > buflen) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + framelen = (size_t)rv; + + frame->hd.flags = flags; + frame->hd.length = framelen; + + assert(buf->pos == buf->last); + buf->last += framelen; + buf->pos -= NGHTTP2_FRAME_HDLEN; + + nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); + + return 0; +} + /* * This function serializes frame for transmission. * @@ -1956,8 +1994,18 @@ static int session_prep_frame(nghttp2_session *session, nghttp2_frame_pack_window_update(&session->aob.framebufs, &frame->window_update); break; + case NGHTTP2_CONTINUATION: + /* We never handle CONTINUATION here. */ + assert(0); + break; default: - return NGHTTP2_ERR_INVALID_ARGUMENT; + /* extension frame */ + rv = session_pack_extension(session, &session->aob.framebufs, frame); + if (rv != 0) { + return rv; + } + + break; } return 0; } else { @@ -3016,6 +3064,47 @@ static int session_call_on_header(nghttp2_session *session, return 0; } +static int +session_call_on_extension_chunk_recv_callback(nghttp2_session *session, + const uint8_t *data, size_t len) { + int rv; + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + if (session->callbacks.on_extension_chunk_recv_callback) { + rv = session->callbacks.on_extension_chunk_recv_callback( + session, &frame->hd, data, len, session->user_data); + if (rv == NGHTTP2_ERR_CANCEL) { + return rv; + } + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + + return 0; +} + +static int session_call_unpack_extension_callback(nghttp2_session *session) { + int rv; + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + void *payload = NULL; + + rv = session->callbacks.unpack_extension_callback( + session, &payload, &frame->hd, session->user_data); + if (rv == NGHTTP2_ERR_CANCEL) { + return rv; + } + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + frame->ext.payload = payload; + + return 0; +} + /* * Handles frame size error. * @@ -4383,6 +4472,24 @@ static int session_process_window_update_frame(nghttp2_session *session) { return nghttp2_session_on_window_update_received(session, frame); } +static int session_process_extension_frame(nghttp2_session *session) { + int rv; + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + rv = session_call_unpack_extension_callback(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + /* This handles the case where rv == NGHTTP2_ERR_CANCEL as well */ + if (rv != 0) { + return 0; + } + + return session_call_on_frame_received(session, frame); +} + int nghttp2_session_on_data_received(nghttp2_session *session, nghttp2_frame *frame) { int rv = 0; @@ -5184,11 +5291,19 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, default: DEBUGF(fprintf(stderr, "recv: unknown frame\n")); - /* Silently ignore unknown frame type. */ + if (!session->callbacks.unpack_extension_callback) { + /* Silently ignore unknown frame type. */ + + busy = 1; + + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + + break; + } busy = 1; - iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + iframe->state = NGHTTP2_IB_READ_EXTENSION_PAYLOAD; break; } @@ -5886,6 +6001,44 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, break; case NGHTTP2_IB_IGN_ALL: return (ssize_t)inlen; + case NGHTTP2_IB_READ_EXTENSION_PAYLOAD: + DEBUGF(fprintf(stderr, "recv: [IB_READ_EXTENSION_PAYLOAD]\n")); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + iframe->payloadleft -= readlen; + in += readlen; + + DEBUGF(fprintf(stderr, "recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft)); + + if (readlen > 0) { + rv = session_call_on_extension_chunk_recv_callback( + session, in - readlen, readlen); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (rv != 0) { + busy = 1; + + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + + break; + } + } + + if (iframe->payloadleft > 0) { + break; + } + + rv = session_process_extension_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + session_inbound_frame_reset(session); + + break; } if (!busy && in == last) { diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index 67860b20..a996f8fd 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -102,7 +102,8 @@ typedef enum { NGHTTP2_IB_READ_PAD_DATA, NGHTTP2_IB_READ_DATA, NGHTTP2_IB_IGN_DATA, - NGHTTP2_IB_IGN_ALL + NGHTTP2_IB_IGN_ALL, + NGHTTP2_IB_READ_EXTENSION_PAYLOAD } nghttp2_inbound_state; #define NGHTTP2_INBOUND_NUM_IV 7 diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c index 763b4038..7dc29ed8 100644 --- a/lib/nghttp2_submit.c +++ b/lib/nghttp2_submit.c @@ -479,3 +479,40 @@ ssize_t nghttp2_pack_settings_payload(uint8_t *buf, size_t buflen, return (ssize_t)nghttp2_frame_pack_settings_payload(buf, iv, niv); } + +int nghttp2_submit_extension(nghttp2_session *session, uint8_t type, + uint8_t flags, int32_t stream_id, void *payload) { + int rv; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_mem *mem; + + mem = &session->mem; + + if (type <= NGHTTP2_CONTINUATION) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (!session->callbacks.pack_extension_callback) { + return NGHTTP2_ERR_INVALID_STATE; + } + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + nghttp2_frame_extension_init(&frame->ext, type, flags, stream_id, payload); + + rv = nghttp2_session_add_item(session, item); + if (rv != 0) { + nghttp2_frame_extension_free(&frame->ext); + nghttp2_mem_free(mem, item); + return rv; + } + + return 0; +} diff --git a/src/app_helper.cc b/src/app_helper.cc index 2ce51af7..c360a7de 100644 --- a/src/app_helper.cc +++ b/src/app_helper.cc @@ -121,7 +121,7 @@ const char *strsettingsid(int32_t id) { } // namespace namespace { -const char *strframetype(uint8_t type) { +std::string strframetype(uint8_t type) { switch (type) { case NGHTTP2_DATA: return "DATA"; @@ -141,9 +141,13 @@ const char *strframetype(uint8_t type) { return "GOAWAY"; case NGHTTP2_WINDOW_UPDATE: return "WINDOW_UPDATE"; - default: - return "UNKNOWN"; } + + std::string s = "UNKNOWN(0x"; + s += util::format_hex(&type, 1); + s += ")"; + + return s; }; } // namespace @@ -280,7 +284,7 @@ const char *frame_name_ansi_esc(print_type ptype) { namespace { void print_frame(print_type ptype, const nghttp2_frame *frame) { fprintf(outfile, "%s%s%s frame ", frame_name_ansi_esc(ptype), - strframetype(frame->hd.type), ansi_escend()); + strframetype(frame->hd.type).c_str(), ansi_escend()); print_frame_hd(frame->hd); if (frame->hd.flags) { print_frame_attr_indent(); From d9893d014c4e1ed061472bbac63c46324582a213 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 11 Oct 2015 17:23:01 +0900 Subject: [PATCH 02/13] Add tests --- tests/main.c | 3 + tests/nghttp2_session_test.c | 189 +++++++++++++++++++++++++++++++++++ tests/nghttp2_session_test.h | 2 + 3 files changed, 194 insertions(+) diff --git a/tests/main.c b/tests/main.c index 64710066..afcf5390 100644 --- a/tests/main.c +++ b/tests/main.c @@ -97,6 +97,8 @@ int main(int argc _U_, char *argv[] _U_) { test_nghttp2_session_recv_settings_header_table_size) || !CU_add_test(pSuite, "session_recv_too_large_frame_length", test_nghttp2_session_recv_too_large_frame_length) || + !CU_add_test(pSuite, "session_recv_extension", + test_nghttp2_session_recv_extension) || !CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) || !CU_add_test(pSuite, "session_add_frame", test_nghttp2_session_add_frame) || @@ -186,6 +188,7 @@ int main(int argc _U_, char *argv[] _U_) { test_nghttp2_submit_shutdown_notice) || !CU_add_test(pSuite, "submit_invalid_nv", test_nghttp2_submit_invalid_nv) || + !CU_add_test(pSuite, "submit_extension", test_nghttp2_submit_extension) || !CU_add_test(pSuite, "session_open_stream", test_nghttp2_session_open_stream) || !CU_add_test(pSuite, "session_open_stream_with_idle_stream_dep", diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index c2376b30..d34b1569 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -58,6 +58,7 @@ typedef struct { scripted_data_feed *df; int frame_recv_cb_called, invalid_frame_recv_cb_called; uint8_t recv_frame_type; + nghttp2_frame_hd recv_frame_hd; int frame_send_cb_called; uint8_t sent_frame_type; int frame_not_send_cb_called; @@ -77,6 +78,7 @@ typedef struct { size_t data_chunk_len; size_t padlen; int begin_frame_cb_called; + nghttp2_buf scratchbuf; } my_user_data; static const nghttp2_nv reqnv[] = { @@ -179,6 +181,8 @@ static int on_frame_recv_callback(nghttp2_session *session _U_, my_user_data *ud = (my_user_data *)user_data; ++ud->frame_recv_cb_called; ud->recv_frame_type = frame->hd.type; + ud->recv_frame_hd = frame->hd; + return 0; } @@ -430,6 +434,55 @@ static int on_stream_close_callback(nghttp2_session *session _U_, return 0; } +static ssize_t pack_extension_callback(nghttp2_session *session _U_, + uint8_t *flags _U_, uint8_t *buf, + size_t len _U_, + const nghttp2_frame *frame, + void *user_data _U_) { + nghttp2_buf *p = frame->ext.payload; + + memcpy(buf, p->pos, nghttp2_buf_len(p)); + + return (ssize_t)nghttp2_buf_len(p); +} + +static int on_extension_chunk_recv_callback(nghttp2_session *session _U_, + const nghttp2_frame_hd *hd _U_, + const uint8_t *data, size_t len, + void *user_data) { + my_user_data *my_data = (my_user_data *)user_data; + nghttp2_buf *buf = &my_data->scratchbuf; + + buf->last = nghttp2_cpymem(buf->last, data, len); + + return 0; +} + +static int cancel_on_extension_chunk_recv_callback( + nghttp2_session *session _U_, const nghttp2_frame_hd *hd _U_, + const uint8_t *data _U_, size_t len _U_, void *user_data _U_) { + return NGHTTP2_ERR_CANCEL; +} + +static int unpack_extension_callback(nghttp2_session *session _U_, + void **payload, + const nghttp2_frame_hd *hd _U_, + void *user_data) { + my_user_data *my_data = (my_user_data *)user_data; + nghttp2_buf *buf = &my_data->scratchbuf; + + *payload = buf; + + return 0; +} + +static int cancel_unpack_extension_callback(nghttp2_session *session _U_, + void **payload _U_, + const nghttp2_frame_hd *hd _U_, + void *user_data _U_) { + return NGHTTP2_ERR_CANCEL; +} + static nghttp2_settings_entry *dup_iv(const nghttp2_settings_entry *iv, size_t niv) { return nghttp2_frame_iv_copy(iv, niv, nghttp2_mem_default()); @@ -1707,6 +1760,81 @@ void test_nghttp2_session_recv_too_large_frame_length(void) { nghttp2_session_del(session); } +void test_nghttp2_session_recv_extension(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_buf buf; + nghttp2_frame_hd hd; + nghttp2_mem *mem; + const char data[] = "Hello World!"; + ssize_t rv; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + callbacks.on_extension_chunk_recv_callback = on_extension_chunk_recv_callback; + callbacks.unpack_extension_callback = unpack_extension_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_buf_init2(&ud.scratchbuf, 4096, mem); + nghttp2_buf_init2(&buf, 4096, mem); + + nghttp2_frame_hd_init(&hd, sizeof(data), 111, 0xab, 1000000007); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + buf.last = nghttp2_cpymem(buf.last, data, sizeof(data)); + + nghttp2_session_client_new(&session, &callbacks, &ud); + + nghttp2_frame_hd_init(&ud.recv_frame_hd, 0, 0, 0, 0); + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + hd.length == (size_t)rv); + CU_ASSERT(111 == ud.recv_frame_hd.type); + CU_ASSERT(0xab == ud.recv_frame_hd.flags); + CU_ASSERT(1000000007 == ud.recv_frame_hd.stream_id); + CU_ASSERT(0 == memcmp(data, ud.scratchbuf.pos, sizeof(data))); + + nghttp2_session_del(session); + + /* cancel in on_extension_chunk_recv_callback */ + nghttp2_buf_reset(&ud.scratchbuf); + + callbacks.on_extension_chunk_recv_callback = + cancel_on_extension_chunk_recv_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + hd.length == (size_t)rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* cancel in unpack_extension_callback */ + nghttp2_buf_reset(&ud.scratchbuf); + + callbacks.on_extension_chunk_recv_callback = on_extension_chunk_recv_callback; + callbacks.unpack_extension_callback = cancel_unpack_extension_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + hd.length == (size_t)rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + nghttp2_buf_free(&buf, mem); + nghttp2_buf_free(&ud.scratchbuf, mem); +} + void test_nghttp2_session_continue(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; @@ -4691,6 +4819,67 @@ void test_nghttp2_submit_invalid_nv(void) { nghttp2_session_del(session); } +void test_nghttp2_submit_extension(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + accumulator acc; + nghttp2_mem *mem; + const char data[] = "Hello World!"; + size_t len; + int32_t stream_id; + int rv; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + callbacks.pack_extension_callback = pack_extension_callback; + callbacks.send_callback = accumulator_send_callback; + + nghttp2_buf_init2(&ud.scratchbuf, 4096, mem); + + nghttp2_session_client_new(&session, &callbacks, &ud); + + ud.scratchbuf.last = nghttp2_cpymem(ud.scratchbuf.last, data, sizeof(data)); + ud.acc = &acc; + + rv = nghttp2_submit_extension(session, 211, 0x01, 3, &ud.scratchbuf); + + CU_ASSERT(0 == rv); + + acc.length = 0; + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + sizeof(data) == acc.length); + + len = nghttp2_get_uint32(acc.buf) >> 8; + + CU_ASSERT(sizeof(data) == len); + CU_ASSERT(211 == acc.buf[3]); + CU_ASSERT(0x01 == acc.buf[4]); + + stream_id = (int32_t)nghttp2_get_uint32(acc.buf + 5); + + CU_ASSERT(3 == stream_id); + CU_ASSERT(0 == memcmp(data, &acc.buf[NGHTTP2_FRAME_HDLEN], sizeof(data))); + + nghttp2_session_del(session); + + /* submitting standard HTTP/2 frame is error */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + rv = nghttp2_submit_extension(session, NGHTTP2_GOAWAY, NGHTTP2_FLAG_NONE, 0, + NULL); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + nghttp2_session_del(session); + nghttp2_buf_free(&ud.scratchbuf, mem); +} + void test_nghttp2_session_open_stream(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index ce08b5f9..80f0cfc9 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -38,6 +38,7 @@ void test_nghttp2_session_recv_unknown_frame(void); void test_nghttp2_session_recv_unexpected_continuation(void); void test_nghttp2_session_recv_settings_header_table_size(void); void test_nghttp2_session_recv_too_large_frame_length(void); +void test_nghttp2_session_recv_extension(void); void test_nghttp2_session_continue(void); void test_nghttp2_session_add_frame(void); void test_nghttp2_session_on_request_headers_received(void); @@ -85,6 +86,7 @@ void test_nghttp2_submit_window_update(void); void test_nghttp2_submit_window_update_local_window_size(void); void test_nghttp2_submit_shutdown_notice(void); void test_nghttp2_submit_invalid_nv(void); +void test_nghttp2_submit_extension(void); void test_nghttp2_session_open_stream(void); void test_nghttp2_session_open_stream_with_idle_stream_dep(void); void test_nghttp2_session_get_next_ob_item(void); From 061a557839207028593015c5f0bd3ae4b81018bd Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 15 Oct 2015 00:17:07 +0900 Subject: [PATCH 03/13] Add nghttp2_option_set_user_recv_extension_type to opt-in incoming extension type --- lib/includes/nghttp2/nghttp2.h | 15 +++++++++++++++ lib/nghttp2_option.c | 10 ++++++++++ lib/nghttp2_option.h | 7 ++++++- lib/nghttp2_session.c | 9 ++++++++- lib/nghttp2_session.h | 6 ++++++ tests/nghttp2_session_test.c | 12 +++++++++--- 6 files changed, 54 insertions(+), 5 deletions(-) diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 91030f28..e098bcce 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -2186,6 +2186,21 @@ NGHTTP2_EXTERN void nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option, uint32_t val); +/** + * @function + * + * Set extension frame type the application is willing to handle with + * user defined callbacks (see + * :type:`nghttp2_on_extension_chunk_recv_callback` and + * :type:`nghttp2_unpack_extension_callback`). The |type| is + * extension frame type, and must be strictly greater than 0x9. + * Otherwise, this function does nothing. The application does not + * have to call this function if it just sends extension frames. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_user_recv_extension_type(nghttp2_option *option, + uint8_t type); + /** * @function * diff --git a/lib/nghttp2_option.c b/lib/nghttp2_option.c index 04dbbc6a..81ae6ba7 100644 --- a/lib/nghttp2_option.c +++ b/lib/nghttp2_option.c @@ -62,3 +62,13 @@ void nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option, option->opt_set_mask |= NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS; option->max_reserved_remote_streams = val; } + +void nghttp2_option_set_user_recv_extension_type(nghttp2_option *option, + uint8_t type) { + if (type < 10) { + return; + } + + option->opt_set_mask |= NGHTTP2_OPT_USER_RECV_EXT_TYPES; + option->user_recv_ext_types[type / 8] |= 1 << (7 - (type & 0x7)); +} diff --git a/lib/nghttp2_option.h b/lib/nghttp2_option.h index ebf416ac..a2d090fb 100644 --- a/lib/nghttp2_option.h +++ b/lib/nghttp2_option.h @@ -59,7 +59,8 @@ typedef enum { NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 1, NGHTTP2_OPT_NO_RECV_CLIENT_MAGIC = 1 << 2, NGHTTP2_OPT_NO_HTTP_MESSAGING = 1 << 3, - NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS = 1 << 4 + NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS = 1 << 4, + NGHTTP2_OPT_USER_RECV_EXT_TYPES = 1 << 5 } nghttp2_option_flag; /** @@ -91,6 +92,10 @@ struct nghttp2_option { * NGHTTP2_OPT_NO_HTTP_MESSAGING */ int no_http_messaging; + /** + * NGHTTP2_OPT_USER_RECV_EXT_TYPES + */ + uint8_t user_recv_ext_types[32]; }; #endif /* NGHTTP2_OPTION_H */ diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 787bc311..1e8e81e5 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -402,6 +402,11 @@ static int session_new(nghttp2_session **session_ptr, (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_HTTP_MESSAGING; } + + if (option->opt_set_mask & NGHTTP2_OPT_USER_RECV_EXT_TYPES) { + memcpy((*session_ptr)->user_recv_ext_types, option->user_recv_ext_types, + sizeof((*session_ptr)->user_recv_ext_types)); + } } (*session_ptr)->callbacks = *callbacks; @@ -5291,7 +5296,9 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, default: DEBUGF(fprintf(stderr, "recv: unknown frame\n")); - if (!session->callbacks.unpack_extension_callback) { + if (!session->callbacks.unpack_extension_callback || + (session->user_recv_ext_types[iframe->frame.hd.type / 8] & + (1 << (7 - (iframe->frame.hd.type & 0x7)))) == 0) { /* Silently ignore unknown frame type. */ busy = 1; diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index a996f8fd..73c10cdf 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -296,6 +296,12 @@ struct nghttp2_session { this session. The nonzero does not necessarily mean WINDOW_UPDATE is not queued. */ uint8_t window_update_queued; + /* Bitfield of extension frame types that application is willing to + receive. First most significant 10 bits are standard frame types + and not used. If bit is set, it indicates that incoming frame + with that type is passed to user defined callbacks, otherwise + they are ignored. */ + uint8_t user_recv_ext_types[32]; }; /* Struct used when updating initial window size of each active diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index d34b1569..7ed6af65 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -1769,6 +1769,7 @@ void test_nghttp2_session_recv_extension(void) { nghttp2_mem *mem; const char data[] = "Hello World!"; ssize_t rv; + nghttp2_option *option; mem = nghttp2_mem_default(); @@ -1778,6 +1779,9 @@ void test_nghttp2_session_recv_extension(void) { callbacks.unpack_extension_callback = unpack_extension_callback; callbacks.on_frame_recv_callback = on_frame_recv_callback; + nghttp2_option_new(&option); + nghttp2_option_set_user_recv_extension_type(option, 111); + nghttp2_buf_init2(&ud.scratchbuf, 4096, mem); nghttp2_buf_init2(&buf, 4096, mem); @@ -1786,7 +1790,7 @@ void test_nghttp2_session_recv_extension(void) { buf.last += NGHTTP2_FRAME_HDLEN; buf.last = nghttp2_cpymem(buf.last, data, sizeof(data)); - nghttp2_session_client_new(&session, &callbacks, &ud); + nghttp2_session_client_new2(&session, &callbacks, &ud, option); nghttp2_frame_hd_init(&ud.recv_frame_hd, 0, 0, 0, 0); rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); @@ -1805,7 +1809,7 @@ void test_nghttp2_session_recv_extension(void) { callbacks.on_extension_chunk_recv_callback = cancel_on_extension_chunk_recv_callback; - nghttp2_session_server_new(&session, &callbacks, &ud); + nghttp2_session_server_new2(&session, &callbacks, &ud, option); ud.frame_recv_cb_called = 0; rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); @@ -1821,7 +1825,7 @@ void test_nghttp2_session_recv_extension(void) { callbacks.on_extension_chunk_recv_callback = on_extension_chunk_recv_callback; callbacks.unpack_extension_callback = cancel_unpack_extension_callback; - nghttp2_session_server_new(&session, &callbacks, &ud); + nghttp2_session_server_new2(&session, &callbacks, &ud, option); ud.frame_recv_cb_called = 0; rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); @@ -1833,6 +1837,8 @@ void test_nghttp2_session_recv_extension(void) { nghttp2_buf_free(&buf, mem); nghttp2_buf_free(&ud.scratchbuf, mem); + + nghttp2_option_del(option); } void test_nghttp2_session_continue(void) { From 837e71630600df6854f9d9fffc075787602a2a6e Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 15 Oct 2015 00:30:42 +0900 Subject: [PATCH 04/13] Fix compile error with gcc --- lib/nghttp2_option.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/nghttp2_option.c b/lib/nghttp2_option.c index 81ae6ba7..2201ec6e 100644 --- a/lib/nghttp2_option.c +++ b/lib/nghttp2_option.c @@ -70,5 +70,6 @@ void nghttp2_option_set_user_recv_extension_type(nghttp2_option *option, } option->opt_set_mask |= NGHTTP2_OPT_USER_RECV_EXT_TYPES; - option->user_recv_ext_types[type / 8] |= 1 << (7 - (type & 0x7)); + option->user_recv_ext_types[type / 8] = (uint8_t)( + option->user_recv_ext_types[type / 8] | (1 << (7 - (type & 0x7)))); } From 83cc2511e33ef3bd486261454030c038b10852cc Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 17 Nov 2015 21:29:21 +0900 Subject: [PATCH 05/13] Remove flags parameter from nghttp2_pack_extension_callback It has no usecase at the moment. It is most likely that applications know the flags when it submitted extension frame, no need to modify it later. Possibly feature bloat. --- lib/includes/nghttp2/nghttp2.h | 11 ++++------- lib/nghttp2_session.c | 7 ++----- tests/nghttp2_session_test.c | 3 +-- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 926d730d..09e2dba5 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -1747,11 +1747,9 @@ typedef int (*nghttp2_unpack_extension_callback)(nghttp2_session *session, * extension payload in its wire format. The frame header will be * packed by library. Application must pack payload only. * frame->ext.payload is the object passed to - * `nghttp2_submit_extension()` as payload parameter. The |*flags| is - * initialized to the flags parameter passed in - * `nghttp2_submit_extension()`. Application can modify flags value - * if it wants. Application must pack extension payload to the |buf| - * of its capacity |len| bytes. + * `nghttp2_submit_extension()` as payload parameter. Application + * must pack extension payload to the |buf| of its capacity |len| + * bytes. * * The implementation of this function returns the number of bytes * written into |buf| when it succeeds. @@ -1770,8 +1768,7 @@ typedef int (*nghttp2_unpack_extension_callback)(nghttp2_session *session, * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. */ typedef ssize_t (*nghttp2_pack_extension_callback)(nghttp2_session *session, - uint8_t *flags, uint8_t *buf, - size_t len, + uint8_t *buf, size_t len, const nghttp2_frame *frame, void *user_data); diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 9c6d34ac..84f90a5b 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -1738,19 +1738,17 @@ static size_t session_estimate_headers_payload(nghttp2_session *session, static int session_pack_extension(nghttp2_session *session, nghttp2_bufs *bufs, nghttp2_frame *frame) { ssize_t rv; - uint8_t flags; nghttp2_buf *buf; size_t buflen; size_t framelen; assert(session->callbacks.pack_extension_callback); - flags = frame->hd.flags; buf = &bufs->head->buf; buflen = nghttp2_min(nghttp2_buf_avail(buf), NGHTTP2_MAX_PAYLOADLEN); - rv = session->callbacks.pack_extension_callback( - session, &flags, buf->last, buflen, frame, session->user_data); + rv = session->callbacks.pack_extension_callback(session, buf->last, buflen, + frame, session->user_data); if (rv == NGHTTP2_ERR_CANCEL) { return (int)rv; } @@ -1761,7 +1759,6 @@ static int session_pack_extension(nghttp2_session *session, nghttp2_bufs *bufs, framelen = (size_t)rv; - frame->hd.flags = flags; frame->hd.length = framelen; assert(buf->pos == buf->last); diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index dcde6d93..86c9e711 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -435,8 +435,7 @@ static int on_stream_close_callback(nghttp2_session *session _U_, } static ssize_t pack_extension_callback(nghttp2_session *session _U_, - uint8_t *flags _U_, uint8_t *buf, - size_t len _U_, + uint8_t *buf, size_t len _U_, const nghttp2_frame *frame, void *user_data _U_) { nghttp2_buf *p = frame->ext.payload; From 0248d979fe7a71d35d7938944c04e270f76421ae Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 10 Jan 2016 17:08:03 +0900 Subject: [PATCH 06/13] Add missing nghttp2_option_set_user_recv_extension_type.rst --- doc/Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/Makefile.am b/doc/Makefile.am index 065b4c24..a0de3cb0 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -58,6 +58,7 @@ APIDOCS= \ nghttp2_option_set_no_http_messaging.rst \ nghttp2_option_set_no_recv_client_magic.rst \ nghttp2_option_set_peer_max_concurrent_streams.rst \ + nghttp2_option_set_user_recv_extension_type.rst \ nghttp2_pack_settings_payload.rst \ nghttp2_priority_spec_check_default.rst \ nghttp2_priority_spec_default_init.rst \ From 304ff6a6f98c7437a5a7f31d14f6db4991290f7d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 7 Feb 2016 21:12:36 +0900 Subject: [PATCH 07/13] Don't send extension frame in closing state --- lib/nghttp2_session.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 35be7cf5..4a905ef5 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -2034,6 +2034,10 @@ static int session_prep_frame(nghttp2_session *session, break; default: /* extension frame */ + if (session_is_closing(session)) { + return NGHTTP2_ERR_SESSION_CLOSING; + } + rv = session_pack_extension(session, &session->aob.framebufs, frame); if (rv != 0) { return rv; From 9aee43f7d8378895db32f34951a86586baac1793 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 24 Feb 2016 23:51:00 +0900 Subject: [PATCH 08/13] Update doc for extension frames --- lib/includes/nghttp2/nghttp2.h | 45 +++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 6a2a5c13..9b753a71 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -1736,11 +1736,19 @@ typedef int (*nghttp2_on_extension_chunk_recv_callback)( * :type:`nghttp2_on_extension_chunk_recv_callback`. The frame header * is already unpacked by the library and provided as |hd|. * + * To receive extension frames, the application must tell desired + * extension frame type to the library using + * `nghttp2_option_set_user_recv_extension_type()`. + * * The implementation of this function may store the pointer to the * created object as a result of unpacking in |*payload|, and returns * 0. The pointer stored in |*payload| is opaque to the library, and - * the library does not own its pointer. |*payload| is initialled as - * `NULL`. + * the library does not own its pointer. |*payload| is initialized as + * `NULL`. The |*payload| is available as ``frame->ext.payload`` in + * :type:`nghttp2_on_frame_recv_callback`. Therefore if application + * can free that memory inside :type:`nghttp2_on_frame_recv_callback` + * callback. Of course, application has a liberty not ot use + * |*payload|, and do its own mechanism to process extension frames. * * To abort processing this extension frame, return * :enum:`NGHTTP2_ERR_CANCEL`. @@ -1760,16 +1768,16 @@ typedef int (*nghttp2_unpack_extension_callback)(nghttp2_session *session, /** * @functypedef * - * Callbck function invoked when library asks the application to pack + * Callback function invoked when library asks the application to pack * extension payload in its wire format. The frame header will be * packed by library. Application must pack payload only. - * frame->ext.payload is the object passed to + * ``frame->ext.payload`` is the object passed to * `nghttp2_submit_extension()` as payload parameter. Application * must pack extension payload to the |buf| of its capacity |len| - * bytes. + * bytes. The |len| is at least 16KiB. * - * The implementation of this function returns the number of bytes - * written into |buf| when it succeeds. + * The implementation of this function should return the number of + * bytes written into |buf| when it succeeds. * * To abort processing this extension frame, return * :enum:`NGHTTP2_ERR_CANCEL`, and @@ -2229,13 +2237,15 @@ nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option, /** * @function * - * Set extension frame type the application is willing to handle with + * Sets extension frame type the application is willing to handle with * user defined callbacks (see * :type:`nghttp2_on_extension_chunk_recv_callback` and * :type:`nghttp2_unpack_extension_callback`). The |type| is * extension frame type, and must be strictly greater than 0x9. - * Otherwise, this function does nothing. The application does not - * have to call this function if it just sends extension frames. + * Otherwise, this function does nothing. The application can call + * this function multiple times to set more than one frame type to + * receive. The application does not have to call this function if it + * just sends extension frames. */ NGHTTP2_EXTERN void nghttp2_option_set_user_recv_extension_type(nghttp2_option *option, @@ -3916,12 +3926,23 @@ NGHTTP2_EXTERN int nghttp2_submit_window_update(nghttp2_session *session, * * Submits extension frame. * - * Application can pass arbitrary frame flags and stream ID to |flags| + * Application can pass arbitrary frame flags and stream ID in |flags| * and |stream_id| respectively. The |payload| is opaque pointer, and - * it can be accessible though frame->ext.payload in + * it can be accessible though ``frame->ext.payload`` in * :type:`nghttp2_pack_extension_callback`. The library will not own * passed |payload| pointer. * + * The application must set :type:`nghttp2_pack_extension_callback` + * using `nghttp2_session_callbacks_set_pack_extension_callback()`. + * + * The application should retain the memory pointed by |payload| until + * the transmission of extension frame is done (which is indicated by + * :type:`nghttp2_on_frame_send_callback`), or transmission fails + * (which is indicated by :type:`nghttp2_on_frame_not_send_callback`). + * If application does not touch this memory region after packing it + * into a wire format, application can free it inside + * :type:`nghttp2_pack_extension_callback`. + * * The standard HTTP/2 frame cannot be sent with this function, so * |type| must be strictly grater than 0x9. Otherwise, this function * will fail with error code :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`. From 827abb57e98ad624abccbb8f05e22217119e81e8 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 24 Feb 2016 23:59:01 +0900 Subject: [PATCH 09/13] Simplified bitfield calculation of extension frame --- lib/nghttp2_option.c | 4 ++-- lib/nghttp2_session.c | 2 +- lib/nghttp2_session.h | 9 +++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/nghttp2_option.c b/lib/nghttp2_option.c index 2201ec6e..fd665112 100644 --- a/lib/nghttp2_option.c +++ b/lib/nghttp2_option.c @@ -70,6 +70,6 @@ void nghttp2_option_set_user_recv_extension_type(nghttp2_option *option, } option->opt_set_mask |= NGHTTP2_OPT_USER_RECV_EXT_TYPES; - option->user_recv_ext_types[type / 8] = (uint8_t)( - option->user_recv_ext_types[type / 8] | (1 << (7 - (type & 0x7)))); + option->user_recv_ext_types[type / 8] = + (uint8_t)(option->user_recv_ext_types[type / 8] | (1 << (type & 0x7))); } diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 6be3c631..6f3843c8 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -5345,7 +5345,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, if (!session->callbacks.unpack_extension_callback || (session->user_recv_ext_types[iframe->frame.hd.type / 8] & - (1 << (7 - (iframe->frame.hd.type & 0x7)))) == 0) { + (1 << (iframe->frame.hd.type & 0x7))) == 0) { /* Silently ignore unknown frame type. */ busy = 1; diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index 0bf12db5..c319c15e 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -306,10 +306,11 @@ struct nghttp2_session { WINDOW_UPDATE is not queued. */ uint8_t window_update_queued; /* Bitfield of extension frame types that application is willing to - receive. First most significant 10 bits are standard frame types - and not used. If bit is set, it indicates that incoming frame - with that type is passed to user defined callbacks, otherwise - they are ignored. */ + receive. To designate the bit of given frame type i, use + user_recv_ext_types[i / 8] & (1 << (i & 0x7)). First 10 frame + types are standard frame types and not used in this bitfield. If + bit is set, it indicates that incoming frame with that type is + passed to user defined callbacks, otherwise they are ignored. */ uint8_t user_recv_ext_types[32]; }; From ebfae904abb47cea04b357b0a7570164b4483c45 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 25 Feb 2016 00:32:17 +0900 Subject: [PATCH 10/13] Fix typo --- lib/includes/nghttp2/nghttp2.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 9b753a71..329a605d 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -1744,7 +1744,7 @@ typedef int (*nghttp2_on_extension_chunk_recv_callback)( * created object as a result of unpacking in |*payload|, and returns * 0. The pointer stored in |*payload| is opaque to the library, and * the library does not own its pointer. |*payload| is initialized as - * `NULL`. The |*payload| is available as ``frame->ext.payload`` in + * ``NULL``. The |*payload| is available as ``frame->ext.payload`` in * :type:`nghttp2_on_frame_recv_callback`. Therefore if application * can free that memory inside :type:`nghttp2_on_frame_recv_callback` * callback. Of course, application has a liberty not ot use From dbffb8995b3624484e3567bcd639d4af7daf7f34 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 25 Feb 2016 00:45:24 +0900 Subject: [PATCH 11/13] Handle extension frame in session_inbound_frame_reset --- lib/nghttp2_session.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 6f3843c8..fffdf45d 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -231,6 +231,8 @@ static void session_inbound_frame_reset(nghttp2_session *session) { nghttp2_session_new(), we rely on the fact that iframe->frame.hd.type is 0, so that no free is performed. */ switch (iframe->frame.hd.type) { + case NGHTTP2_DATA: + break; case NGHTTP2_HEADERS: nghttp2_frame_headers_free(&iframe->frame.headers, mem); break; @@ -255,6 +257,10 @@ static void session_inbound_frame_reset(nghttp2_session *session) { case NGHTTP2_WINDOW_UPDATE: nghttp2_frame_window_update_free(&iframe->frame.window_update); break; + default: + /* extension frame */ + nghttp2_frame_extension_free(&iframe->frame.ext); + break; } memset(&iframe->frame, 0, sizeof(nghttp2_frame)); From 56bdfd1df20116ecd756a070f13fba2d0336889f Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 25 Feb 2016 00:58:24 +0900 Subject: [PATCH 12/13] Revert "Handle extension frame in session_inbound_frame_reset" This reverts commit dbffb8995b3624484e3567bcd639d4af7daf7f34. --- lib/nghttp2_session.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index fffdf45d..6f3843c8 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -231,8 +231,6 @@ static void session_inbound_frame_reset(nghttp2_session *session) { nghttp2_session_new(), we rely on the fact that iframe->frame.hd.type is 0, so that no free is performed. */ switch (iframe->frame.hd.type) { - case NGHTTP2_DATA: - break; case NGHTTP2_HEADERS: nghttp2_frame_headers_free(&iframe->frame.headers, mem); break; @@ -257,10 +255,6 @@ static void session_inbound_frame_reset(nghttp2_session *session) { case NGHTTP2_WINDOW_UPDATE: nghttp2_frame_window_update_free(&iframe->frame.window_update); break; - default: - /* extension frame */ - nghttp2_frame_extension_free(&iframe->frame.ext); - break; } memset(&iframe->frame, 0, sizeof(nghttp2_frame)); From 8aac5d6af2e081644baa74f3eb16ed234a990e17 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 25 Feb 2016 00:58:50 +0900 Subject: [PATCH 13/13] Update doc --- lib/nghttp2_frame.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index 3d057847..ebf2e5e6 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -191,7 +191,9 @@ void nghttp2_frame_extension_init(nghttp2_extension *frame, uint8_t type, frame->payload = payload; } -void nghttp2_frame_extension_free(nghttp2_extension *frame _U_) {} +void nghttp2_frame_extension_free(nghttp2_extension *frame _U_) { + /* should be noop for performance reason */ +} size_t nghttp2_frame_priority_len(uint8_t flags) { if (flags & NGHTTP2_FLAG_PRIORITY) {