Merge branch 'altsvc'

This commit is contained in:
Tatsuhiro Tsujikawa 2016-04-09 19:29:17 +09:00
commit 9028512a5f
19 changed files with 983 additions and 27 deletions

View File

@ -51,3 +51,4 @@ Resources
* HTTP/2 https://tools.ietf.org/html/rfc7540
* HPACK https://tools.ietf.org/html/rfc7541
* HTTP Alternative Services https://tools.ietf.org/html/rfc7838

View File

@ -591,7 +591,12 @@ typedef enum {
* callbacks because the library processes this frame type and its
* preceding HEADERS/PUSH_PROMISE as a single frame.
*/
NGHTTP2_CONTINUATION = 0x09
NGHTTP2_CONTINUATION = 0x09,
/**
* The ALTSVC frame, which is defined in `RFC 7383
* <https://tools.ietf.org/html/rfc7838#section-4>`_.
*/
NGHTTP2_ALTSVC = 0x0a
} nghttp2_frame_type;
/**
@ -2373,6 +2378,26 @@ NGHTTP2_EXTERN void
nghttp2_option_set_user_recv_extension_type(nghttp2_option *option,
uint8_t type);
/**
* @function
*
* Sets extension frame type the application is willing to receive
* using builtin handler. The |type| is the extension frame type to
* receive, 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.
*
* If same frame type is passed to both
* `nghttp2_option_set_builtin_recv_extension_type()` and
* `nghttp2_option_set_user_recv_extension_type()`, the latter takes
* precedence.
*/
NGHTTP2_EXTERN void
nghttp2_option_set_builtin_recv_extension_type(nghttp2_option *option,
uint8_t type);
/**
* @function
*
@ -4113,6 +4138,80 @@ NGHTTP2_EXTERN int nghttp2_submit_extension(nghttp2_session *session,
uint8_t type, uint8_t flags,
int32_t stream_id, void *payload);
/**
* @struct
*
* The payload of ALTSVC frame. ALTSVC frame is a non-critical
* extension to HTTP/2. If this frame is received, and
* `nghttp2_option_set_user_recv_extension_type()` is not set, and
* `nghttp2_option_set_builtin_recv_extension_type()` is set for
* :enum:`NGHTTP2_ALTSVC`, ``nghttp2_extension.payload`` will point to
* this struct.
*
* It has the following members:
*/
typedef struct {
/**
* The pointer to origin which this alternative service is
* associated with. This is not necessarily NULL-terminated.
*/
uint8_t *origin;
/**
* The length of the |origin|.
*/
size_t origin_len;
/**
* The pointer to Alt-Svc field value contained in ALTSVC frame.
* This is not necessarily NULL-terminated.
*/
uint8_t *field_value;
/**
* The length of the |field_value|.
*/
size_t field_value_len;
} nghttp2_ext_altsvc;
/**
* @function
*
* Submits ALTSVC frame.
*
* ALTSVC frame is a non-critical extension to HTTP/2, and defined in
* is defined in `RFC 7383
* <https://tools.ietf.org/html/rfc7838#section-4>`_.
*
* The |flags| is currently ignored and should be
* :enum:`NGHTTP2_FLAG_NONE`.
*
* The |origin| points to the origin this alternative service is
* associated with. The |origin_len| is the length of the origin. If
* |stream_id| is 0, the origin must be specified. If |stream_id| is
* not zero, the origin must be empty (in other words, |origin_len|
* must be 0).
*
* The ALTSVC frame is only usable from server side. If this function
* is invoked with client side session, this function returns
* :enum:`NGHTTP2_ERR_INVALID_STATE`.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* :enum:`NGHTTP2_ERR_NOMEM`
* Out of memory
* :enum:`NGHTTP2_ERR_INVALID_STATE`
* The function is called from client side session
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
* The sum of |origin_len| and |field_value_len| is larger than
* 16382; or |origin_len| is 0 while |stream_id| is 0; or
* |origin_len| is not 0 while |stream_id| is not 0.
*/
NGHTTP2_EXTERN int nghttp2_submit_altsvc(nghttp2_session *session,
uint8_t flags, int32_t stream_id,
const uint8_t *origin,
size_t origin_len,
const uint8_t *field_value,
size_t field_value_len);
/**
* @function
*

View File

@ -193,6 +193,30 @@ void nghttp2_frame_extension_init(nghttp2_extension *frame, uint8_t type,
void nghttp2_frame_extension_free(nghttp2_extension *frame _U_) {}
void nghttp2_frame_altsvc_init(nghttp2_extension *frame, int32_t stream_id,
uint8_t *origin, size_t origin_len,
uint8_t *field_value, size_t field_value_len) {
nghttp2_ext_altsvc *altsvc;
nghttp2_frame_hd_init(&frame->hd, 2 + origin_len + field_value_len,
NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, stream_id);
altsvc = frame->payload;
altsvc->origin = origin;
altsvc->origin_len = origin_len;
altsvc->field_value = field_value;
altsvc->field_value_len = field_value_len;
}
void nghttp2_frame_altsvc_free(nghttp2_extension *frame, nghttp2_mem *mem) {
nghttp2_ext_altsvc *altsvc;
altsvc = frame->payload;
/* We use the same buffer for altsvc->origin and
altsvc->field_value. */
nghttp2_mem_free(mem, altsvc->origin);
}
size_t nghttp2_frame_priority_len(uint8_t flags) {
if (flags & NGHTTP2_FLAG_PRIORITY) {
return NGHTTP2_PRIORITY_SPECLEN;
@ -668,6 +692,55 @@ void nghttp2_frame_unpack_window_update_payload(nghttp2_window_update *frame,
nghttp2_get_uint32(payload) & NGHTTP2_WINDOW_SIZE_INCREMENT_MASK;
}
int nghttp2_frame_pack_altsvc(nghttp2_bufs *bufs, nghttp2_extension *frame) {
int rv;
nghttp2_buf *buf;
nghttp2_ext_altsvc *altsvc;
altsvc = frame->payload;
buf = &bufs->head->buf;
assert(nghttp2_buf_avail(buf) >=
2 + altsvc->origin_len + altsvc->field_value_len);
buf->pos -= NGHTTP2_FRAME_HDLEN;
nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd);
nghttp2_put_uint16be(buf->last, (uint16_t)altsvc->origin_len);
buf->last += 2;
rv = nghttp2_bufs_add(bufs, altsvc->origin, altsvc->origin_len);
assert(rv == 0);
rv = nghttp2_bufs_add(bufs, altsvc->field_value, altsvc->field_value_len);
assert(rv == 0);
return 0;
}
void nghttp2_frame_unpack_altsvc_payload(nghttp2_extension *frame,
size_t origin_len, uint8_t *payload,
size_t payloadlen) {
nghttp2_ext_altsvc *altsvc;
uint8_t *p;
altsvc = frame->payload;
p = payload;
altsvc->origin = p;
p += origin_len;
altsvc->origin_len = origin_len;
altsvc->field_value = p;
altsvc->field_value_len = (size_t)(payload + payloadlen - p);
}
nghttp2_settings_entry *nghttp2_frame_iv_copy(const nghttp2_settings_entry *iv,
size_t niv, nghttp2_mem *mem) {
nghttp2_settings_entry *iv_copy;

View File

@ -72,7 +72,7 @@
#define NGHTTP2_MAX_PADLEN 256
/* Union of extension frame payload */
typedef union { int dummy; } nghttp2_ext_frame_payload;
typedef union { nghttp2_ext_altsvc altsvc; } nghttp2_ext_frame_payload;
void nghttp2_frame_pack_frame_hd(uint8_t *buf, const nghttp2_frame_hd *hd);
@ -367,6 +367,27 @@ void nghttp2_frame_unpack_window_update_payload(nghttp2_window_update *frame,
const uint8_t *payload,
size_t payloadlen);
/*
* Packs ALTSVC frame |frame| in wire frame format and store it in
* |bufs|.
*
* The caller must make sure that nghttp2_bufs_reset(bufs) is called
* before calling this function.
*
* This function always succeeds and returns 0.
*/
int nghttp2_frame_pack_altsvc(nghttp2_bufs *bufs, nghttp2_extension *ext);
/*
* Unpacks ALTSVC wire format into |frame|. The |payload| of
* |payloadlen| bytes contains frame payload. This function assumes
* that frame->payload points to the nghttp2_ext_altsvc object.
*
* This function always succeeds and returns 0.
*/
void nghttp2_frame_unpack_altsvc_payload(nghttp2_extension *frame,
size_t origin_len, uint8_t *payload,
size_t payloadlen);
/*
* Initializes HEADERS frame |frame| with given values. |frame| takes
* ownership of |nva|, so caller must not free it. If |stream_id| is
@ -445,6 +466,25 @@ void nghttp2_frame_extension_init(nghttp2_extension *frame, uint8_t type,
void nghttp2_frame_extension_free(nghttp2_extension *frame);
/*
* Initializes ALTSVC frame |frame| with given values. This function
* assumes that frame->payload points to nghttp2_ext_altsvc object.
* Also |origin| and |field_value| are allocated in single buffer,
* starting |origin|. On success, this function takes ownership of
* |origin|, so caller must not free it.
*/
void nghttp2_frame_altsvc_init(nghttp2_extension *frame, int32_t stream_id,
uint8_t *origin, size_t origin_len,
uint8_t *field_value, size_t field_value_len);
/*
* Frees up resources under |frame|. This function does not free
* nghttp2_ext_altsvc object pointed by frame->payload. This function
* only frees origin pointed by nghttp2_ext_altsvc.origin. Therefore,
* other fields must be allocated in the same buffer with origin.
*/
void nghttp2_frame_altsvc_free(nghttp2_extension *frame, nghttp2_mem *mem);
/*
* Returns the number of padding bytes after payload. The total
* padding length is given in the |padlen|. The returned value does

View File

@ -24,6 +24,8 @@
*/
#include "nghttp2_option.h"
#include "nghttp2_session.h"
int nghttp2_option_new(nghttp2_option **option_ptr) {
*option_ptr = calloc(1, sizeof(nghttp2_option));
@ -63,6 +65,10 @@ void nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option,
option->max_reserved_remote_streams = val;
}
static void set_ext_type(uint8_t *ext_types, uint8_t type) {
ext_types[type / 8] = (uint8_t)(ext_types[type / 8] | (1 << (type & 0x7)));
}
void nghttp2_option_set_user_recv_extension_type(nghttp2_option *option,
uint8_t type) {
if (type < 10) {
@ -70,8 +76,19 @@ 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 << (type & 0x7)));
set_ext_type(option->user_recv_ext_types, type);
}
void nghttp2_option_set_builtin_recv_extension_type(nghttp2_option *option,
uint8_t type) {
switch (type) {
case NGHTTP2_ALTSVC:
option->opt_set_mask |= NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES;
option->builtin_recv_ext_types |= NGHTTP2_TYPEMASK_ALTSVC;
return;
default:
return;
}
}
void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option, int val) {

View File

@ -61,7 +61,8 @@ typedef enum {
NGHTTP2_OPT_NO_HTTP_MESSAGING = 1 << 3,
NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS = 1 << 4,
NGHTTP2_OPT_USER_RECV_EXT_TYPES = 1 << 5,
NGHTTP2_OPT_NO_AUTO_PING_ACK = 1 << 6
NGHTTP2_OPT_NO_AUTO_PING_ACK = 1 << 6,
NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES = 1 << 7
} nghttp2_option_flag;
/**
@ -81,6 +82,10 @@ struct nghttp2_option {
* NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS
*/
uint32_t max_reserved_remote_streams;
/**
* NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES
*/
uint32_t builtin_recv_ext_types;
/**
* NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE
*/

View File

@ -72,9 +72,25 @@ 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;
default: {
nghttp2_ext_aux_data *aux_data;
aux_data = &item->aux_data.ext;
if (aux_data->builtin == 0) {
nghttp2_frame_extension_free(&frame->ext);
break;
}
switch (frame->hd.type) {
case NGHTTP2_ALTSVC:
nghttp2_frame_altsvc_free(&frame->ext, mem);
break;
default:
assert(0);
break;
}
}
}
}

View File

@ -87,11 +87,19 @@ typedef struct {
uint8_t flags;
} nghttp2_goaway_aux_data;
/* struct used for extension frame */
typedef struct {
/* nonzero if this extension frame is serialized by library
function, instead of user-defined callbacks. */
uint8_t builtin;
} nghttp2_ext_aux_data;
/* Additional data which cannot be stored in nghttp2_frame struct */
typedef union {
nghttp2_data_aux_data data;
nghttp2_headers_aux_data headers;
nghttp2_goaway_aux_data goaway;
nghttp2_ext_aux_data ext;
} nghttp2_aux_data;
struct nghttp2_outbound_item;
@ -99,6 +107,9 @@ typedef struct nghttp2_outbound_item nghttp2_outbound_item;
struct nghttp2_outbound_item {
nghttp2_frame frame;
/* Storage for extension frame payload. frame->ext.payload points
to this structure to avoid frequent memory allocation. */
nghttp2_ext_frame_payload ext_frame_payload;
nghttp2_aux_data aux_data;
/* The priority used in priority comparion. Smaller is served
ealier. For PING, SETTINGS and non-DATA frames (excluding

View File

@ -142,6 +142,10 @@ static int session_detect_idle_stream(nghttp2_session *session,
return 0;
}
static int check_ext_type_set(const uint8_t *ext_types, uint8_t type) {
return (ext_types[type / 8] & (1 << (type & 0x7))) > 0;
}
static int session_call_error_callback(nghttp2_session *session,
const char *fmt, ...) {
size_t bufsize;
@ -316,7 +320,20 @@ static void session_inbound_frame_reset(nghttp2_session *session) {
break;
default:
/* extension frame */
nghttp2_frame_extension_free(&iframe->frame.ext);
if (check_ext_type_set(session->user_recv_ext_types,
iframe->frame.hd.type)) {
nghttp2_frame_extension_free(&iframe->frame.ext);
} else {
switch (iframe->frame.hd.type) {
case NGHTTP2_ALTSVC:
if ((session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ALTSVC) == 0) {
break;
}
nghttp2_frame_altsvc_free(&iframe->frame.ext, mem);
break;
}
}
break;
}
@ -332,6 +349,8 @@ static void session_inbound_frame_reset(nghttp2_session *session) {
nghttp2_buf_free(&iframe->lbuf, mem);
nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0);
iframe->raw_lbuf = NULL;
iframe->niv = 0;
iframe->payloadleft = 0;
iframe->padlen = 0;
@ -474,6 +493,10 @@ static int session_new(nghttp2_session **session_ptr,
sizeof((*session_ptr)->user_recv_ext_types));
}
if (option->opt_set_mask & NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES) {
(*session_ptr)->builtin_recv_ext_types = option->builtin_recv_ext_types;
}
if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_PING_ACK) &&
option->no_auto_ping_ack) {
(*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_AUTO_PING_ACK;
@ -1664,6 +1687,26 @@ static int session_predicate_window_update_send(nghttp2_session *session,
return 0;
}
static int session_predicate_altsvc_send(nghttp2_session *session,
int32_t stream_id) {
nghttp2_stream *stream;
if (session_is_closing(session)) {
return NGHTTP2_ERR_SESSION_CLOSING;
}
if (stream_id == 0) {
return 0;
}
stream = nghttp2_session_get_stream(session, stream_id);
if (stream == NULL) {
return NGHTTP2_ERR_STREAM_CLOSED;
}
return 0;
}
/* Take into account settings max frame size and both connection-level
flow control here */
static ssize_t
@ -2103,19 +2146,45 @@ static int session_prep_frame(nghttp2_session *session,
/* We never handle CONTINUATION here. */
assert(0);
break;
default:
default: {
nghttp2_ext_aux_data *aux_data;
/* extension frame */
if (session_is_closing(session)) {
return NGHTTP2_ERR_SESSION_CLOSING;
aux_data = &item->aux_data.ext;
if (aux_data->builtin == 0) {
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;
}
rv = session_pack_extension(session, &session->aob.framebufs, frame);
if (rv != 0) {
return rv;
switch (frame->hd.type) {
case NGHTTP2_ALTSVC:
rv = session_predicate_altsvc_send(session, frame->hd.stream_id);
if (rv != 0) {
return rv;
}
nghttp2_frame_pack_altsvc(&session->aob.framebufs, &frame->ext);
break;
default:
/* Unreachable here */
assert(0);
break;
}
break;
}
}
return 0;
} else {
size_t next_readmax;
@ -4591,6 +4660,52 @@ static int session_process_window_update_frame(nghttp2_session *session) {
return nghttp2_session_on_window_update_received(session, frame);
}
int nghttp2_session_on_altsvc_received(nghttp2_session *session,
nghttp2_frame *frame) {
nghttp2_ext_altsvc *altsvc;
nghttp2_stream *stream;
altsvc = frame->ext.payload;
/* session->server case has been excluded */
if (frame->hd.stream_id == 0) {
if (altsvc->origin_len == 0) {
return 0;
}
} else {
if (altsvc->origin_len > 0) {
return 0;
}
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if (!stream) {
return 0;
}
if (stream->state == NGHTTP2_STREAM_CLOSING) {
return 0;
}
}
return session_call_on_frame_received(session, frame);
}
static int session_process_altsvc_frame(nghttp2_session *session) {
nghttp2_inbound_frame *iframe = &session->iframe;
nghttp2_frame *frame = &iframe->frame;
nghttp2_frame_unpack_altsvc_payload(
&frame->ext, nghttp2_get_uint16(iframe->sbuf.pos), iframe->lbuf.pos,
nghttp2_buf_len(&iframe->lbuf));
/* nghttp2_frame_unpack_altsvc_payload steals buffer from
iframe->lbuf */
nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0);
return nghttp2_session_on_altsvc_received(session, frame);
}
static int session_process_extension_frame(nghttp2_session *session) {
int rv;
nghttp2_inbound_frame *iframe = &session->iframe;
@ -5434,25 +5549,66 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
break;
default:
DEBUGF(fprintf(stderr, "recv: unknown frame\n"));
DEBUGF(fprintf(stderr, "recv: extension frame\n"));
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. */
if (check_ext_type_set(session->user_recv_ext_types,
iframe->frame.hd.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;
} else {
switch (iframe->frame.hd.type) {
case NGHTTP2_ALTSVC:
if ((session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ALTSVC) ==
0) {
busy = 1;
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
break;
}
DEBUGF(fprintf(stderr, "recv: ALTSVC\n"));
iframe->frame.hd.flags = NGHTTP2_FLAG_NONE;
iframe->frame.ext.payload = &iframe->ext_frame_payload.altsvc;
if (session->server) {
busy = 1;
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
break;
}
if (iframe->payloadleft < 2) {
busy = 1;
iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
break;
}
busy = 1;
iframe->state = NGHTTP2_IB_READ_NBYTE;
inbound_frame_set_mark(iframe, 2);
break;
default:
busy = 1;
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
break;
}
}
busy = 1;
iframe->state = NGHTTP2_IB_READ_EXTENSION_PAYLOAD;
break;
}
if (!on_begin_frame_called) {
@ -5662,6 +5818,37 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
session_inbound_frame_reset(session);
break;
case NGHTTP2_ALTSVC: {
size_t origin_len;
origin_len = nghttp2_get_uint16(iframe->sbuf.pos);
DEBUGF(fprintf(stderr, "recv: origin_len=%zu\n", origin_len));
if (2 + origin_len > iframe->payloadleft) {
busy = 1;
iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
break;
}
if (iframe->frame.hd.length > 2) {
iframe->raw_lbuf =
nghttp2_mem_malloc(mem, iframe->frame.hd.length - 2);
if (iframe->raw_lbuf == NULL) {
return NGHTTP2_ERR_NOMEM;
}
nghttp2_buf_wrap_init(&iframe->lbuf, iframe->raw_lbuf,
iframe->frame.hd.length);
}
busy = 1;
iframe->state = NGHTTP2_IB_READ_ALTSVC_PAYLOAD;
break;
}
default:
/* This is unknown frame */
session_inbound_frame_reset(session);
@ -6187,6 +6374,36 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
session_inbound_frame_reset(session);
break;
case NGHTTP2_IB_READ_ALTSVC_PAYLOAD:
DEBUGF(fprintf(stderr, "recv: [IB_READ_ALTSVC_PAYLOAD]\n"));
readlen = inbound_frame_payload_readlen(iframe, in, last);
if (readlen > 0) {
iframe->lbuf.last = nghttp2_cpymem(iframe->lbuf.last, in, readlen);
iframe->payloadleft -= readlen;
in += readlen;
}
DEBUGF(fprintf(stderr, "recv: readlen=%zu, payloadleft=%zu\n", readlen,
iframe->payloadleft));
if (iframe->payloadleft) {
assert(nghttp2_buf_avail(&iframe->lbuf) > 0);
break;
}
rv = session_process_altsvc_frame(session);
if (nghttp2_is_fatal(rv)) {
return rv;
}
session_inbound_frame_reset(session);
break;
}

View File

@ -54,6 +54,15 @@ typedef enum {
NGHTTP2_OPTMASK_NO_AUTO_PING_ACK = 1 << 3
} nghttp2_optmask;
/*
* bitmask for built-in type to enable the default handling for that
* type of the frame.
*/
typedef enum {
NGHTTP2_TYPEMASK_NONE = 0,
NGHTTP2_TYPEMASK_ALTSVC = 1 << 0
} nghttp2_typemask;
typedef enum {
NGHTTP2_OB_POP_ITEM,
NGHTTP2_OB_SEND_DATA,
@ -107,6 +116,7 @@ typedef enum {
NGHTTP2_IB_READ_DATA,
NGHTTP2_IB_IGN_DATA,
NGHTTP2_IB_IGN_ALL,
NGHTTP2_IB_READ_ALTSVC_PAYLOAD,
NGHTTP2_IB_READ_EXTENSION_PAYLOAD
} nghttp2_inbound_state;
@ -294,6 +304,9 @@ struct nghttp2_session {
/* Unacked local SETTINGS_MAX_CONCURRENT_STREAMS value. We use this
to refuse the incoming stream if it exceeds this value. */
uint32_t pending_local_max_concurrent_stream;
/* The bitwose OR of zero or more of nghttp2_typemask to indicate
that the default handling of extension frame is enabled. */
uint32_t builtin_recv_ext_types;
/* Unacked local ENABLE_PUSH value. We use this to refuse
PUSH_PROMISE before SETTINGS ACK is received. */
uint8_t pending_enable_push;
@ -716,6 +729,19 @@ int nghttp2_session_on_goaway_received(nghttp2_session *session,
int nghttp2_session_on_window_update_received(nghttp2_session *session,
nghttp2_frame *frame);
/*
* Called when ALTSVC is recieved, assuming |frame| is properly
* initialized.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* NGHTTP2_ERR_CALLBACK_FAILURE
* The callback function failed.
*/
int nghttp2_session_on_altsvc_received(nghttp2_session *session,
nghttp2_frame *frame);
/*
* Called when DATA is received, assuming |frame| is properly
* initialized.

View File

@ -410,6 +410,90 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags,
return 0;
}
int nghttp2_submit_altsvc(nghttp2_session *session, uint8_t flags _U_,
int32_t stream_id, const uint8_t *origin,
size_t origin_len, const uint8_t *field_value,
size_t field_value_len) {
nghttp2_mem *mem;
uint8_t *buf, *p;
uint8_t *origin_copy;
uint8_t *field_value_copy;
nghttp2_outbound_item *item;
nghttp2_frame *frame;
nghttp2_ext_altsvc *altsvc;
int rv;
mem = &session->mem;
if (!session->server) {
return NGHTTP2_ERR_INVALID_STATE;
}
if (2 + origin_len + field_value_len > NGHTTP2_MAX_PAYLOADLEN) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
if (stream_id == 0) {
if (origin_len == 0) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
} else if (origin_len != 0) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
buf = nghttp2_mem_malloc(mem, origin_len + field_value_len + 2);
if (buf == NULL) {
return NGHTTP2_ERR_NOMEM;
}
p = buf;
origin_copy = p;
if (origin_len) {
p = nghttp2_cpymem(p, origin, origin_len);
}
*p++ = '\0';
field_value_copy = p;
if (field_value_len) {
p = nghttp2_cpymem(p, field_value, field_value_len);
}
*p++ = '\0';
item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
if (item == NULL) {
rv = NGHTTP2_ERR_NOMEM;
goto fail_item_malloc;
}
nghttp2_outbound_item_init(item);
item->aux_data.ext.builtin = 1;
altsvc = &item->ext_frame_payload.altsvc;
frame = &item->frame;
frame->ext.payload = altsvc;
nghttp2_frame_altsvc_init(&frame->ext, stream_id, origin_copy, origin_len,
field_value_copy, field_value_len);
rv = nghttp2_session_add_item(session, item);
if (rv != 0) {
nghttp2_frame_altsvc_free(&frame->ext, mem);
nghttp2_mem_free(mem, item);
return rv;
}
return 0;
fail_item_malloc:
free(buf);
return rv;
}
static uint8_t set_request_flags(const nghttp2_priority_spec *pri_spec,
const nghttp2_data_provider *data_prd) {
uint8_t flags = NGHTTP2_FLAG_NONE;

View File

@ -104,6 +104,8 @@ std::string strframetype(uint8_t type) {
return "GOAWAY";
case NGHTTP2_WINDOW_UPDATE:
return "WINDOW_UPDATE";
case NGHTTP2_ALTSVC:
return "ALTSVC";
}
std::string s = "extension(0x";
@ -339,6 +341,14 @@ void print_frame(print_type ptype, const nghttp2_frame *frame) {
fprintf(outfile, "(window_size_increment=%d)\n",
frame->window_update.window_size_increment);
break;
case NGHTTP2_ALTSVC: {
auto altsvc = static_cast<nghttp2_ext_altsvc *>(frame->ext.payload);
print_frame_attr_indent();
fprintf(outfile, "(origin=[%.*s], altsvc_field_value=[%.*s])\n",
static_cast<int>(altsvc->origin_len), altsvc->origin,
static_cast<int>(altsvc->field_value_len), altsvc->field_value);
break;
}
default:
break;
}

View File

@ -118,6 +118,7 @@ Config::Config()
nghttp2_option_new(&http2_option);
nghttp2_option_set_peer_max_concurrent_streams(http2_option,
peer_max_concurrent_streams);
nghttp2_option_set_builtin_recv_extension_type(http2_option, NGHTTP2_ALTSVC);
}
Config::~Config() { nghttp2_option_del(http2_option); }

View File

@ -55,6 +55,8 @@ int main(int argc _U_, char *argv[] _U_) {
/* add the tests to the suite */
if (!CU_add_test(pSuite, "failmalloc_session_send",
test_nghttp2_session_send) ||
!CU_add_test(pSuite, "failmalloc_session_send_server",
test_nghttp2_session_send_server) ||
!CU_add_test(pSuite, "failmalloc_session_recv",
test_nghttp2_session_recv) ||
!CU_add_test(pSuite, "failmalloc_frame", test_nghttp2_frame) ||

View File

@ -217,6 +217,50 @@ void test_nghttp2_session_send(void) {
TEST_FAILMALLOC_RUN(run_nghttp2_session_send);
}
static void run_nghttp2_session_send_server(void) {
nghttp2_session *session;
nghttp2_session_callbacks *callbacks;
int rv;
const uint8_t *txdata;
ssize_t txdatalen;
rv = nghttp2_session_callbacks_new(&callbacks);
if (rv != 0) {
return;
}
rv = nghttp2_session_server_new3(&session, callbacks, NULL, NULL,
nghttp2_mem_fm());
nghttp2_session_callbacks_del(callbacks);
if (rv != 0) {
return;
}
const uint8_t origin[] = "nghttp2.org";
const uint8_t altsvc_field_value[] = "h2=\":443\"";
rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 0, origin,
sizeof(origin) - 1, altsvc_field_value,
sizeof(altsvc_field_value) - 1);
if (rv != 0) {
goto fail;
}
txdatalen = nghttp2_session_mem_send(session, &txdata);
if (txdatalen < 0) {
goto fail;
}
fail:
nghttp2_session_del(session);
}
void test_nghttp2_session_send_server(void) {
TEST_FAILMALLOC_RUN(run_nghttp2_session_send_server);
}
static void run_nghttp2_session_recv(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;

View File

@ -30,6 +30,7 @@
#endif /* HAVE_CONFIG_H */
void test_nghttp2_session_send(void);
void test_nghttp2_session_send_server(void);
void test_nghttp2_session_recv(void);
void test_nghttp2_frame(void);
void test_nghttp2_hd(void);

View File

@ -103,6 +103,8 @@ int main(int argc _U_, char *argv[] _U_) {
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_recv_altsvc",
test_nghttp2_session_recv_altsvc) ||
!CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) ||
!CU_add_test(pSuite, "session_add_frame",
test_nghttp2_session_add_frame) ||
@ -132,6 +134,8 @@ int main(int argc _U_, char *argv[] _U_) {
test_nghttp2_session_on_data_received) ||
!CU_add_test(pSuite, "session_on_data_received_fail_fast",
test_nghttp2_session_on_data_received_fail_fast) ||
!CU_add_test(pSuite, "session_on_altsvc_received",
test_nghttp2_session_on_altsvc_received) ||
!CU_add_test(pSuite, "session_send_headers_start_stream",
test_nghttp2_session_send_headers_start_stream) ||
!CU_add_test(pSuite, "session_send_headers_reply",
@ -197,6 +201,7 @@ int main(int argc _U_, char *argv[] _U_) {
!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, "submit_altsvc", test_nghttp2_submit_altsvc) ||
!CU_add_test(pSuite, "session_open_stream",
test_nghttp2_session_open_stream) ||
!CU_add_test(pSuite, "session_open_stream_with_idle_stream_dep",

View File

@ -1955,6 +1955,124 @@ void test_nghttp2_session_recv_extension(void) {
nghttp2_option_del(option);
}
void test_nghttp2_session_recv_altsvc(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
my_user_data ud;
nghttp2_buf buf;
nghttp2_frame_hd hd;
nghttp2_mem *mem;
ssize_t rv;
nghttp2_option *option;
static const uint8_t origin[] = "nghttp2.org";
static const uint8_t field_value[] = "h2=\":443\"";
mem = nghttp2_mem_default();
nghttp2_buf_init2(&buf, NGHTTP2_FRAME_HDLEN + NGHTTP2_MAX_FRAME_SIZE_MIN,
mem);
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.on_frame_recv_callback = on_frame_recv_callback;
nghttp2_option_new(&option);
nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ALTSVC);
nghttp2_session_client_new2(&session, &callbacks, &ud, option);
nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1 + sizeof(field_value) - 1,
NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, 0);
nghttp2_frame_pack_frame_hd(buf.last, &hd);
buf.last += NGHTTP2_FRAME_HDLEN;
nghttp2_put_uint16be(buf.last, sizeof(origin) - 1);
buf.last += 2;
buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1);
buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1);
ud.frame_recv_cb_called = 0;
rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf));
CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv);
CU_ASSERT(1 == ud.frame_recv_cb_called);
CU_ASSERT(NGHTTP2_ALTSVC == ud.recv_frame_hd.type);
CU_ASSERT(NGHTTP2_FLAG_NONE == ud.recv_frame_hd.flags);
CU_ASSERT(0 == ud.recv_frame_hd.stream_id);
nghttp2_session_del(session);
/* size of origin is larger than frame length */
nghttp2_buf_reset(&buf);
nghttp2_session_client_new2(&session, &callbacks, &ud, option);
nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1 - 1, NGHTTP2_ALTSVC,
NGHTTP2_FLAG_NONE, 0);
nghttp2_frame_pack_frame_hd(buf.last, &hd);
buf.last += NGHTTP2_FRAME_HDLEN;
nghttp2_put_uint16be(buf.last, sizeof(origin) - 1);
buf.last += 2;
buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1 - 1);
ud.frame_recv_cb_called = 0;
rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf));
CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv);
CU_ASSERT(0 == ud.frame_recv_cb_called);
nghttp2_session_del(session);
/* send large frame (16KiB) */
nghttp2_buf_reset(&buf);
nghttp2_session_client_new2(&session, &callbacks, &ud, option);
nghttp2_frame_hd_init(&hd, NGHTTP2_MAX_FRAME_SIZE_MIN, NGHTTP2_ALTSVC,
NGHTTP2_FLAG_NONE, 0);
nghttp2_frame_pack_frame_hd(buf.last, &hd);
buf.last += NGHTTP2_FRAME_HDLEN;
nghttp2_put_uint16be(buf.last, sizeof(origin) - 1);
buf.last += 2;
buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1);
memset(buf.last, 0, nghttp2_buf_avail(&buf));
buf.last += nghttp2_buf_avail(&buf);
ud.frame_recv_cb_called = 0;
rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf));
CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv);
CU_ASSERT(1 == ud.frame_recv_cb_called);
CU_ASSERT(NGHTTP2_ALTSVC == ud.recv_frame_hd.type);
CU_ASSERT(NGHTTP2_MAX_FRAME_SIZE_MIN == ud.recv_frame_hd.length);
nghttp2_session_del(session);
/* received by server */
nghttp2_buf_reset(&buf);
nghttp2_session_server_new2(&session, &callbacks, &ud, option);
nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1 + sizeof(field_value) - 1,
NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, 0);
nghttp2_frame_pack_frame_hd(buf.last, &hd);
buf.last += NGHTTP2_FRAME_HDLEN;
nghttp2_put_uint16be(buf.last, sizeof(origin) - 1);
buf.last += 2;
buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1);
buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1);
ud.frame_recv_cb_called = 0;
rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf));
CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv);
CU_ASSERT(0 == ud.frame_recv_cb_called);
nghttp2_session_del(session);
nghttp2_buf_free(&buf, mem);
nghttp2_option_del(option);
}
void test_nghttp2_session_continue(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
@ -3364,6 +3482,113 @@ void test_nghttp2_session_on_data_received_fail_fast(void) {
nghttp2_session_del(session);
}
void test_nghttp2_session_on_altsvc_received(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
my_user_data ud;
nghttp2_frame frame;
nghttp2_mem *mem;
nghttp2_option *option;
uint8_t origin[] = "nghttp2.org";
uint8_t field_value[] = "h2=\":443\"";
int rv;
mem = nghttp2_mem_default();
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.on_frame_recv_callback = on_frame_recv_callback;
nghttp2_option_new(&option);
nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ALTSVC);
nghttp2_session_client_new2(&session, &callbacks, &ud, option);
frame.ext.payload = &session->iframe.ext_frame_payload;
/* We just pass the strings without making a copy. This is OK,
since we never call nghttp2_frame_altsvc_free(). */
nghttp2_frame_altsvc_init(&frame.ext, 0, origin, sizeof(origin) - 1,
field_value, sizeof(field_value) - 1);
ud.frame_recv_cb_called = 0;
rv = nghttp2_session_on_altsvc_received(session, &frame);
CU_ASSERT(0 == rv);
CU_ASSERT(1 == ud.frame_recv_cb_called);
nghttp2_session_del(session);
/* Receiving empty origin with stream ID == 0 */
nghttp2_session_client_new2(&session, &callbacks, &ud, option);
frame.ext.payload = &session->iframe.ext_frame_payload;
nghttp2_frame_altsvc_init(&frame.ext, 0, origin, 0, field_value,
sizeof(field_value) - 1);
ud.frame_recv_cb_called = 0;
rv = nghttp2_session_on_altsvc_received(session, &frame);
CU_ASSERT(0 == rv);
CU_ASSERT(0 == ud.frame_recv_cb_called);
nghttp2_session_del(session);
/* Receiving non-empty origin with stream ID != 0 */
nghttp2_session_client_new2(&session, &callbacks, &ud, option);
frame.ext.payload = &session->iframe.ext_frame_payload;
open_sent_stream(session, 1);
nghttp2_frame_altsvc_init(&frame.ext, 1, origin, sizeof(origin) - 1,
field_value, sizeof(field_value) - 1);
ud.frame_recv_cb_called = 0;
rv = nghttp2_session_on_altsvc_received(session, &frame);
CU_ASSERT(0 == rv);
CU_ASSERT(0 == ud.frame_recv_cb_called);
nghttp2_session_del(session);
/* Receiving empty origin with stream ID != 0; this is OK */
nghttp2_session_client_new2(&session, &callbacks, &ud, option);
frame.ext.payload = &session->iframe.ext_frame_payload;
open_sent_stream(session, 1);
nghttp2_frame_altsvc_init(&frame.ext, 1, origin, 0, field_value,
sizeof(field_value) - 1);
ud.frame_recv_cb_called = 0;
rv = nghttp2_session_on_altsvc_received(session, &frame);
CU_ASSERT(0 == rv);
CU_ASSERT(1 == ud.frame_recv_cb_called);
nghttp2_session_del(session);
/* Stream does not exist; ALTSVC will be ignored. */
nghttp2_session_client_new2(&session, &callbacks, &ud, option);
frame.ext.payload = &session->iframe.ext_frame_payload;
nghttp2_frame_altsvc_init(&frame.ext, 1, origin, 0, field_value,
sizeof(field_value) - 1);
ud.frame_recv_cb_called = 0;
rv = nghttp2_session_on_altsvc_received(session, &frame);
CU_ASSERT(0 == rv);
CU_ASSERT(0 == ud.frame_recv_cb_called);
nghttp2_session_del(session);
nghttp2_option_del(option);
}
void test_nghttp2_session_send_headers_start_stream(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
@ -5217,6 +5442,82 @@ void test_nghttp2_submit_extension(void) {
nghttp2_buf_free(&ud.scratchbuf, mem);
}
void test_nghttp2_submit_altsvc(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_mem *mem;
my_user_data ud;
int rv;
ssize_t len;
const uint8_t *data;
nghttp2_frame_hd hd;
size_t origin_len;
mem = nghttp2_mem_default();
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
nghttp2_session_server_new(&session, &callbacks, &ud);
const uint8_t origin[] = "nghttp2.org";
const uint8_t field_value[] = "h2=\":443\"";
rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 0, origin,
sizeof(origin) - 1, field_value,
sizeof(field_value) - 1);
CU_ASSERT(0 == rv);
ud.frame_send_cb_called = 0;
len = nghttp2_session_mem_send(session, &data);
CU_ASSERT(len ==
NGHTTP2_FRAME_HDLEN + 2 + sizeof(origin) - 1 + sizeof(field_value) -
1);
nghttp2_frame_unpack_frame_hd(&hd, data);
CU_ASSERT(2 + sizeof(origin) - 1 + sizeof(field_value) - 1 == hd.length);
CU_ASSERT(NGHTTP2_ALTSVC == hd.type);
CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags);
origin_len = nghttp2_get_uint16(data + NGHTTP2_FRAME_HDLEN);
CU_ASSERT(sizeof(origin) - 1 == origin_len);
CU_ASSERT(0 ==
memcmp(origin, data + NGHTTP2_FRAME_HDLEN + 2, sizeof(origin) - 1));
CU_ASSERT(0 == memcmp(field_value,
data + NGHTTP2_FRAME_HDLEN + 2 + sizeof(origin) - 1,
hd.length - (sizeof(origin) - 1) - 2));
/* submitting empty origin with stream_id == 0 is error */
rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 0, NULL, 0,
field_value, sizeof(field_value) - 1);
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
/* submitting non-empty origin with stream_id != 0 is error */
rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 1, origin,
sizeof(origin) - 1, field_value,
sizeof(field_value) - 1);
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
nghttp2_session_del(session);
/* submitting from client side session is error */
nghttp2_session_client_new(&session, &callbacks, NULL);
rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 0, origin,
sizeof(origin) - 1, field_value,
sizeof(field_value) - 1);
CU_ASSERT(NGHTTP2_ERR_INVALID_STATE == rv);
nghttp2_session_del(session);
}
void test_nghttp2_session_open_stream(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;

View File

@ -45,6 +45,7 @@ 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_recv_altsvc(void);
void test_nghttp2_session_continue(void);
void test_nghttp2_session_add_frame(void);
void test_nghttp2_session_on_request_headers_received(void);
@ -60,6 +61,7 @@ void test_nghttp2_session_on_goaway_received(void);
void test_nghttp2_session_on_window_update_received(void);
void test_nghttp2_session_on_data_received(void);
void test_nghttp2_session_on_data_received_fail_fast(void);
void test_nghttp2_session_on_altsvc_received(void);
void test_nghttp2_session_send_headers_start_stream(void);
void test_nghttp2_session_send_headers_reply(void);
void test_nghttp2_session_send_headers_frame_size_error(void);
@ -95,6 +97,7 @@ 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_submit_altsvc(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);