Merge branch 'simple-extensions'

This commit is contained in:
Tatsuhiro Tsujikawa 2016-02-25 01:00:35 +09:00
commit ba34e911e1
17 changed files with 669 additions and 9 deletions

View File

@ -58,6 +58,7 @@ APIDOCS= \
nghttp2_option_set_no_http_messaging.rst \ nghttp2_option_set_no_http_messaging.rst \
nghttp2_option_set_no_recv_client_magic.rst \ nghttp2_option_set_no_recv_client_magic.rst \
nghttp2_option_set_peer_max_concurrent_streams.rst \ nghttp2_option_set_peer_max_concurrent_streams.rst \
nghttp2_option_set_user_recv_extension_type.rst \
nghttp2_pack_settings_payload.rst \ nghttp2_pack_settings_payload.rst \
nghttp2_priority_spec_check_default.rst \ nghttp2_priority_spec_check_default.rst \
nghttp2_priority_spec_default_init.rst \ nghttp2_priority_spec_default_init.rst \
@ -70,16 +71,19 @@ APIDOCS= \
nghttp2_session_callbacks_set_on_begin_frame_callback.rst \ nghttp2_session_callbacks_set_on_begin_frame_callback.rst \
nghttp2_session_callbacks_set_on_begin_headers_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_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_not_send_callback.rst \
nghttp2_session_callbacks_set_on_frame_recv_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_frame_send_callback.rst \
nghttp2_session_callbacks_set_on_header_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_invalid_frame_recv_callback.rst \
nghttp2_session_callbacks_set_on_stream_close_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_recv_callback.rst \
nghttp2_session_callbacks_set_select_padding_callback.rst \ nghttp2_session_callbacks_set_select_padding_callback.rst \
nghttp2_session_callbacks_set_send_callback.rst \ nghttp2_session_callbacks_set_send_callback.rst \
nghttp2_session_callbacks_set_send_data_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_new.rst \
nghttp2_session_client_new2.rst \ nghttp2_session_client_new2.rst \
nghttp2_session_client_new3.rst \ nghttp2_session_client_new3.rst \
@ -131,6 +135,7 @@ APIDOCS= \
nghttp2_stream_get_weight.rst \ nghttp2_stream_get_weight.rst \
nghttp2_strerror.rst \ nghttp2_strerror.rst \
nghttp2_submit_data.rst \ nghttp2_submit_data.rst \
nghttp2_submit_extension.rst \
nghttp2_submit_goaway.rst \ nghttp2_submit_goaway.rst \
nghttp2_submit_headers.rst \ nghttp2_submit_headers.rst \
nghttp2_submit_ping.rst \ nghttp2_submit_ping.rst \

View File

@ -382,6 +382,10 @@ typedef enum {
* Unexpected internal error, but recovered. * Unexpected internal error, but recovered.
*/ */
NGHTTP2_ERR_INTERNAL = -534, 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 * The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is
* under unexpected condition and processing was terminated (e.g., * under unexpected condition and processing was terminated (e.g.,
@ -1700,6 +1704,99 @@ typedef int (*nghttp2_on_begin_frame_callback)(nghttp2_session *session,
const nghttp2_frame_hd *hd, const nghttp2_frame_hd *hd,
void *user_data); 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|.
*
* 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 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`.
*
* 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
*
* 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
* `nghttp2_submit_extension()` as payload parameter. Application
* must pack extension payload to the |buf| of its capacity |len|
* bytes. The |len| is at least 16KiB.
*
* 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
* :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 *buf, size_t len,
const nghttp2_frame *frame,
void *user_data);
struct nghttp2_session_callbacks; struct nghttp2_session_callbacks;
/** /**
@ -1892,6 +1989,37 @@ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_send_data_callback(
nghttp2_session_callbacks *cbs, nghttp2_session_callbacks *cbs,
nghttp2_send_data_callback send_data_callback); 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 * @functypedef
* *
@ -2106,6 +2234,23 @@ NGHTTP2_EXTERN void
nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option, nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option,
uint32_t val); uint32_t val);
/**
* @function
*
* 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 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,
uint8_t type);
/** /**
* @function * @function
* *
@ -3776,6 +3921,48 @@ NGHTTP2_EXTERN int nghttp2_submit_window_update(nghttp2_session *session,
int32_t stream_id, int32_t stream_id,
int32_t window_size_increment); int32_t window_size_increment);
/**
* @function
*
* Submits extension frame.
*
* 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
* :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`.
*
* 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 * @function
* *

View File

@ -127,3 +127,21 @@ void nghttp2_session_callbacks_set_send_data_callback(
nghttp2_send_data_callback send_data_callback) { nghttp2_send_data_callback send_data_callback) {
cbs->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;
}

View File

@ -107,6 +107,9 @@ struct nghttp2_session_callbacks {
*/ */
nghttp2_on_begin_frame_callback on_begin_frame_callback; nghttp2_on_begin_frame_callback on_begin_frame_callback;
nghttp2_send_data_callback send_data_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 */ #endif /* NGHTTP2_CALLBACKS_H */

View File

@ -184,6 +184,17 @@ void nghttp2_frame_data_init(nghttp2_data *frame, uint8_t flags,
void nghttp2_frame_data_free(nghttp2_data *frame _U_) {} 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_) {
/* should be noop for performance reason */
}
size_t nghttp2_frame_priority_len(uint8_t flags) { size_t nghttp2_frame_priority_len(uint8_t flags) {
if (flags & NGHTTP2_FLAG_PRIORITY) { if (flags & NGHTTP2_FLAG_PRIORITY) {
return NGHTTP2_PRIORITY_SPECLEN; return NGHTTP2_PRIORITY_SPECLEN;

View File

@ -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_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 * Returns the number of padding bytes after payload. The total
* padding length is given in the |padlen|. The returned value does * padding length is given in the |padlen|. The returned value does

View File

@ -288,6 +288,8 @@ const char *nghttp2_strerror(int error_code) {
return "Stream was refused"; return "Stream was refused";
case NGHTTP2_ERR_INTERNAL: case NGHTTP2_ERR_INTERNAL:
return "Internal error"; return "Internal error";
case NGHTTP2_ERR_CANCEL:
return "Cancel";
case NGHTTP2_ERR_NOMEM: case NGHTTP2_ERR_NOMEM:
return "Out of memory"; return "Out of memory";
case NGHTTP2_ERR_CALLBACK_FAILURE: case NGHTTP2_ERR_CALLBACK_FAILURE:

View File

@ -62,3 +62,14 @@ void nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option,
option->opt_set_mask |= NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS; option->opt_set_mask |= NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS;
option->max_reserved_remote_streams = val; 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] =
(uint8_t)(option->user_recv_ext_types[type / 8] | (1 << (type & 0x7)));
}

View File

@ -59,7 +59,8 @@ typedef enum {
NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 1, NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 1,
NGHTTP2_OPT_NO_RECV_CLIENT_MAGIC = 1 << 2, NGHTTP2_OPT_NO_RECV_CLIENT_MAGIC = 1 << 2,
NGHTTP2_OPT_NO_HTTP_MESSAGING = 1 << 3, 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; } nghttp2_option_flag;
/** /**
@ -91,6 +92,10 @@ struct nghttp2_option {
* NGHTTP2_OPT_NO_HTTP_MESSAGING * NGHTTP2_OPT_NO_HTTP_MESSAGING
*/ */
int no_http_messaging; int no_http_messaging;
/**
* NGHTTP2_OPT_USER_RECV_EXT_TYPES
*/
uint8_t user_recv_ext_types[32];
}; };
#endif /* NGHTTP2_OPTION_H */ #endif /* NGHTTP2_OPTION_H */

View File

@ -72,6 +72,9 @@ void nghttp2_outbound_item_free(nghttp2_outbound_item *item, nghttp2_mem *mem) {
case NGHTTP2_WINDOW_UPDATE: case NGHTTP2_WINDOW_UPDATE:
nghttp2_frame_window_update_free(&frame->window_update); nghttp2_frame_window_update_free(&frame->window_update);
break; break;
default:
nghttp2_frame_extension_free(&frame->ext);
break;
} }
} }

View File

@ -405,6 +405,11 @@ static int session_new(nghttp2_session **session_ptr,
(*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_HTTP_MESSAGING; (*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; (*session_ptr)->callbacks = *callbacks;
@ -1749,6 +1754,41 @@ static size_t session_estimate_headers_payload(nghttp2_session *session,
additional; additional;
} }
static int session_pack_extension(nghttp2_session *session, nghttp2_bufs *bufs,
nghttp2_frame *frame) {
ssize_t rv;
nghttp2_buf *buf;
size_t buflen;
size_t framelen;
assert(session->callbacks.pack_extension_callback);
buf = &bufs->head->buf;
buflen = nghttp2_min(nghttp2_buf_avail(buf), NGHTTP2_MAX_PAYLOADLEN);
rv = session->callbacks.pack_extension_callback(session, 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.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. * This function serializes frame for transmission.
* *
@ -1989,8 +2029,22 @@ static int session_prep_frame(nghttp2_session *session,
nghttp2_frame_pack_window_update(&session->aob.framebufs, nghttp2_frame_pack_window_update(&session->aob.framebufs,
&frame->window_update); &frame->window_update);
break; break;
case NGHTTP2_CONTINUATION:
/* We never handle CONTINUATION here. */
assert(0);
break;
default: default:
return NGHTTP2_ERR_INVALID_ARGUMENT; /* 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;
}
break;
} }
return 0; return 0;
} else { } else {
@ -3074,6 +3128,47 @@ static int session_call_on_header(nghttp2_session *session,
return 0; 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. * Handles frame size error.
* *
@ -4412,6 +4507,24 @@ static int session_process_window_update_frame(nghttp2_session *session) {
return nghttp2_session_on_window_update_received(session, frame); 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, int nghttp2_session_on_data_received(nghttp2_session *session,
nghttp2_frame *frame) { nghttp2_frame *frame) {
int rv = 0; int rv = 0;
@ -5230,11 +5343,21 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
default: default:
DEBUGF(fprintf(stderr, "recv: unknown frame\n")); DEBUGF(fprintf(stderr, "recv: unknown frame\n"));
/* Silently ignore unknown frame type. */ if (!session->callbacks.unpack_extension_callback ||
(session->user_recv_ext_types[iframe->frame.hd.type / 8] &
(1 << (iframe->frame.hd.type & 0x7))) == 0) {
/* Silently ignore unknown frame type. */
busy = 1;
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
break;
}
busy = 1; busy = 1;
iframe->state = NGHTTP2_IB_IGN_PAYLOAD; iframe->state = NGHTTP2_IB_READ_EXTENSION_PAYLOAD;
break; break;
} }
@ -5934,6 +6057,44 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
break; break;
case NGHTTP2_IB_IGN_ALL: case NGHTTP2_IB_IGN_ALL:
return (ssize_t)inlen; 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) { if (!busy && in == last) {

View File

@ -105,7 +105,8 @@ typedef enum {
NGHTTP2_IB_READ_PAD_DATA, NGHTTP2_IB_READ_PAD_DATA,
NGHTTP2_IB_READ_DATA, NGHTTP2_IB_READ_DATA,
NGHTTP2_IB_IGN_DATA, NGHTTP2_IB_IGN_DATA,
NGHTTP2_IB_IGN_ALL NGHTTP2_IB_IGN_ALL,
NGHTTP2_IB_READ_EXTENSION_PAYLOAD
} nghttp2_inbound_state; } nghttp2_inbound_state;
#define NGHTTP2_INBOUND_NUM_IV 7 #define NGHTTP2_INBOUND_NUM_IV 7
@ -304,6 +305,13 @@ struct nghttp2_session {
this session. The nonzero does not necessarily mean this session. The nonzero does not necessarily mean
WINDOW_UPDATE is not queued. */ WINDOW_UPDATE is not queued. */
uint8_t window_update_queued; uint8_t window_update_queued;
/* Bitfield of extension frame types that application is willing to
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];
}; };
/* Struct used when updating initial window size of each active /* Struct used when updating initial window size of each active

View File

@ -530,3 +530,40 @@ ssize_t nghttp2_pack_settings_payload(uint8_t *buf, size_t buflen,
return (ssize_t)nghttp2_frame_pack_settings_payload(buf, iv, niv); 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;
}

View File

@ -121,7 +121,7 @@ const char *strsettingsid(int32_t id) {
} // namespace } // namespace
namespace { namespace {
const char *strframetype(uint8_t type) { std::string strframetype(uint8_t type) {
switch (type) { switch (type) {
case NGHTTP2_DATA: case NGHTTP2_DATA:
return "DATA"; return "DATA";
@ -141,9 +141,13 @@ const char *strframetype(uint8_t type) {
return "GOAWAY"; return "GOAWAY";
case NGHTTP2_WINDOW_UPDATE: case NGHTTP2_WINDOW_UPDATE:
return "WINDOW_UPDATE"; return "WINDOW_UPDATE";
default:
return "UNKNOWN";
} }
std::string s = "UNKNOWN(0x";
s += util::format_hex(&type, 1);
s += ")";
return s;
}; };
} // namespace } // namespace
@ -280,7 +284,7 @@ const char *frame_name_ansi_esc(print_type ptype) {
namespace { namespace {
void print_frame(print_type ptype, const nghttp2_frame *frame) { void print_frame(print_type ptype, const nghttp2_frame *frame) {
fprintf(outfile, "%s%s%s frame ", frame_name_ansi_esc(ptype), 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); print_frame_hd(frame->hd);
if (frame->hd.flags) { if (frame->hd.flags) {
print_frame_attr_indent(); print_frame_attr_indent();

View File

@ -101,6 +101,8 @@ int main(int argc _U_, char *argv[] _U_) {
test_nghttp2_session_recv_settings_header_table_size) || test_nghttp2_session_recv_settings_header_table_size) ||
!CU_add_test(pSuite, "session_recv_too_large_frame_length", !CU_add_test(pSuite, "session_recv_too_large_frame_length",
test_nghttp2_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_continue", test_nghttp2_session_continue) ||
!CU_add_test(pSuite, "session_add_frame", !CU_add_test(pSuite, "session_add_frame",
test_nghttp2_session_add_frame) || test_nghttp2_session_add_frame) ||
@ -194,6 +196,7 @@ int main(int argc _U_, char *argv[] _U_) {
test_nghttp2_submit_shutdown_notice) || test_nghttp2_submit_shutdown_notice) ||
!CU_add_test(pSuite, "submit_invalid_nv", !CU_add_test(pSuite, "submit_invalid_nv",
test_nghttp2_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", !CU_add_test(pSuite, "session_open_stream",
test_nghttp2_session_open_stream) || test_nghttp2_session_open_stream) ||
!CU_add_test(pSuite, "session_open_stream_with_idle_stream_dep", !CU_add_test(pSuite, "session_open_stream_with_idle_stream_dep",

View File

@ -54,6 +54,7 @@ typedef struct {
scripted_data_feed *df; scripted_data_feed *df;
int frame_recv_cb_called, invalid_frame_recv_cb_called; int frame_recv_cb_called, invalid_frame_recv_cb_called;
uint8_t recv_frame_type; uint8_t recv_frame_type;
nghttp2_frame_hd recv_frame_hd;
int frame_send_cb_called; int frame_send_cb_called;
uint8_t sent_frame_type; uint8_t sent_frame_type;
int frame_not_send_cb_called; int frame_not_send_cb_called;
@ -73,6 +74,7 @@ typedef struct {
size_t data_chunk_len; size_t data_chunk_len;
size_t padlen; size_t padlen;
int begin_frame_cb_called; int begin_frame_cb_called;
nghttp2_buf scratchbuf;
} my_user_data; } my_user_data;
static const nghttp2_nv reqnv[] = { static const nghttp2_nv reqnv[] = {
@ -175,6 +177,8 @@ static int on_frame_recv_callback(nghttp2_session *session _U_,
my_user_data *ud = (my_user_data *)user_data; my_user_data *ud = (my_user_data *)user_data;
++ud->frame_recv_cb_called; ++ud->frame_recv_cb_called;
ud->recv_frame_type = frame->hd.type; ud->recv_frame_type = frame->hd.type;
ud->recv_frame_hd = frame->hd;
return 0; return 0;
} }
@ -416,6 +420,54 @@ static int on_stream_close_callback(nghttp2_session *session _U_,
return 0; return 0;
} }
static ssize_t pack_extension_callback(nghttp2_session *session _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, static nghttp2_settings_entry *dup_iv(const nghttp2_settings_entry *iv,
size_t niv) { size_t niv) {
return nghttp2_frame_iv_copy(iv, niv, nghttp2_mem_default()); return nghttp2_frame_iv_copy(iv, niv, nghttp2_mem_default());
@ -1822,6 +1874,87 @@ void test_nghttp2_session_recv_too_large_frame_length(void) {
nghttp2_session_del(session); 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;
nghttp2_option *option;
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_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);
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_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));
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_new2(&session, &callbacks, &ud, option);
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_new2(&session, &callbacks, &ud, option);
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);
nghttp2_option_del(option);
}
void test_nghttp2_session_continue(void) { void test_nghttp2_session_continue(void) {
nghttp2_session *session; nghttp2_session *session;
nghttp2_session_callbacks callbacks; nghttp2_session_callbacks callbacks;
@ -5005,6 +5138,67 @@ void test_nghttp2_submit_invalid_nv(void) {
nghttp2_session_del(session); 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) { void test_nghttp2_session_open_stream(void) {
nghttp2_session *session; nghttp2_session *session;
nghttp2_session_callbacks callbacks; nghttp2_session_callbacks callbacks;

View File

@ -40,6 +40,7 @@ void test_nghttp2_session_recv_unknown_frame(void);
void test_nghttp2_session_recv_unexpected_continuation(void); void test_nghttp2_session_recv_unexpected_continuation(void);
void test_nghttp2_session_recv_settings_header_table_size(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_too_large_frame_length(void);
void test_nghttp2_session_recv_extension(void);
void test_nghttp2_session_continue(void); void test_nghttp2_session_continue(void);
void test_nghttp2_session_add_frame(void); void test_nghttp2_session_add_frame(void);
void test_nghttp2_session_on_request_headers_received(void); void test_nghttp2_session_on_request_headers_received(void);
@ -89,6 +90,7 @@ void test_nghttp2_submit_window_update(void);
void test_nghttp2_submit_window_update_local_window_size(void); void test_nghttp2_submit_window_update_local_window_size(void);
void test_nghttp2_submit_shutdown_notice(void); void test_nghttp2_submit_shutdown_notice(void);
void test_nghttp2_submit_invalid_nv(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(void);
void test_nghttp2_session_open_stream_with_idle_stream_dep(void); void test_nghttp2_session_open_stream_with_idle_stream_dep(void);
void test_nghttp2_session_get_next_ob_item(void); void test_nghttp2_session_get_next_ob_item(void);