altsvc: Receive ALTSVC frame

This commit is contained in:
Tatsuhiro Tsujikawa 2016-04-03 22:58:25 +09:00
parent efbd48b122
commit 795ee8c20f
10 changed files with 272 additions and 18 deletions

View File

@ -2377,6 +2377,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
*
@ -4123,7 +4143,7 @@ NGHTTP2_EXTERN int nghttp2_submit_extension(nghttp2_session *session,
* The payload of ALTSVC frame. ALTSVC frame is one of extension
* frame. If this frame is received, and
* `nghttp2_option_set_user_recv_extension_type()` is not set, and
* `nghttp2_option_set_recv_extension_type()` is set for
* `nghttp2_option_set_builtin_recv_extension_type()` is set for
* :enum:`NGHTTP2_ALTSVC`, ``nghttp2_extension.payload`` will point to
* this struct.
*

View File

@ -215,7 +215,6 @@ void nghttp2_frame_altsvc_free(nghttp2_extension *frame, nghttp2_mem *mem) {
/* We use the same buffer for altsvc->origin and
altsvc->field_value. */
nghttp2_mem_free(mem, altsvc->origin);
nghttp2_mem_free(mem, altsvc);
}
size_t nghttp2_frame_priority_len(uint8_t flags) {
@ -723,6 +722,25 @@ int nghttp2_frame_pack_altsvc(nghttp2_bufs *bufs, nghttp2_extension *frame) {
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);
@ -378,6 +378,16 @@ void nghttp2_frame_unpack_window_update_payload(nghttp2_window_update *frame,
*/
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
@ -456,10 +466,23 @@ 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);
/*

View File

@ -63,6 +63,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 +74,17 @@ 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) {
if (type < 10) {
return;
}
option->opt_set_mask |= NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES;
set_ext_type(option->builtin_recv_ext_types, type);
}
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;
/**
@ -101,6 +102,10 @@ struct nghttp2_option {
* NGHTTP2_OPT_USER_RECV_EXT_TYPES
*/
uint8_t user_recv_ext_types[32];
/**
* NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES
*/
uint8_t builtin_recv_ext_types[32];
};
#endif /* NGHTTP2_OPTION_H */

View File

@ -90,6 +90,8 @@ void nghttp2_outbound_item_free(nghttp2_outbound_item *item, nghttp2_mem *mem) {
assert(0);
break;
}
nghttp2_mem_free(mem, frame->ext.payload);
}
}
}

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,18 @@ static void session_inbound_frame_reset(nghttp2_session *session) {
break;
default:
/* extension frame */
if (check_ext_type_set(session->user_recv_ext_types,
iframe->frame.hd.type)) {
nghttp2_frame_extension_free(&iframe->frame.ext);
} else if (check_ext_type_set(session->builtin_recv_ext_types,
iframe->frame.hd.type)) {
switch (iframe->frame.hd.type) {
case NGHTTP2_ALTSVC:
nghttp2_frame_altsvc_free(&iframe->frame.ext, mem);
break;
}
}
break;
}
@ -332,6 +347,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 +491,12 @@ 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) {
memcpy((*session_ptr)->builtin_recv_ext_types,
option->builtin_recv_ext_types,
sizeof((*session_ptr)->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;
@ -4637,6 +4660,44 @@ 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;
altsvc = frame->ext.payload;
if (session->server) {
return 0;
}
if (frame->hd.stream_id == 0) {
if (altsvc->origin_len == 0) {
return 0;
}
} else if (altsvc->origin_len > 0) {
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;
frame->ext.payload = &iframe->ext_frame_payload.altsvc;
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;
@ -5480,11 +5541,11 @@ 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) {
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;
@ -5499,6 +5560,40 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
iframe->state = NGHTTP2_IB_READ_EXTENSION_PAYLOAD;
break;
} else if (check_ext_type_set(session->builtin_recv_ext_types,
iframe->frame.hd.type)) {
switch (iframe->frame.hd.type) {
case NGHTTP2_ALTSVC:
DEBUGF(fprintf(stderr, "recv: ALTSVC\n"));
iframe->frame.hd.flags = NGHTTP2_FLAG_NONE;
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;
}
} else {
busy = 1;
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
break;
}
}
if (!on_begin_frame_called) {
@ -5708,6 +5803,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);
@ -6233,6 +6359,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

@ -107,6 +107,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;
@ -313,6 +314,7 @@ struct nghttp2_session {
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];
uint8_t builtin_recv_ext_types[32];
};
/* Struct used when updating initial window size of each active
@ -716,6 +718,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

@ -485,6 +485,7 @@ int nghttp2_submit_altsvc(nghttp2_session *session, uint8_t flags _U_,
rv = nghttp2_session_add_item(session, item);
if (rv != 0) {
nghttp2_frame_altsvc_free(&frame->ext, mem);
nghttp2_mem_free(mem, frame->ext.payload);
nghttp2_mem_free(mem, item);
return rv;

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); }