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, nghttp2_option_set_user_recv_extension_type(nghttp2_option *option,
uint8_t type); 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 * @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 * The payload of ALTSVC frame. ALTSVC frame is one of extension
* frame. If this frame is received, and * frame. If this frame is received, and
* `nghttp2_option_set_user_recv_extension_type()` is not set, 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 * :enum:`NGHTTP2_ALTSVC`, ``nghttp2_extension.payload`` will point to
* this struct. * 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 /* We use the same buffer for altsvc->origin and
altsvc->field_value. */ altsvc->field_value. */
nghttp2_mem_free(mem, altsvc->origin); nghttp2_mem_free(mem, altsvc->origin);
nghttp2_mem_free(mem, altsvc);
} }
size_t nghttp2_frame_priority_len(uint8_t flags) { 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; 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, nghttp2_settings_entry *nghttp2_frame_iv_copy(const nghttp2_settings_entry *iv,
size_t niv, nghttp2_mem *mem) { size_t niv, nghttp2_mem *mem) {
nghttp2_settings_entry *iv_copy; nghttp2_settings_entry *iv_copy;

View File

@ -72,7 +72,7 @@
#define NGHTTP2_MAX_PADLEN 256 #define NGHTTP2_MAX_PADLEN 256
/* Union of extension frame payload */ /* 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); 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); 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 * Initializes HEADERS frame |frame| with given values. |frame| takes
* ownership of |nva|, so caller must not free it. If |stream_id| is * 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); 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, void nghttp2_frame_altsvc_init(nghttp2_extension *frame, int32_t stream_id,
uint8_t *origin, size_t origin_len, uint8_t *origin, size_t origin_len,
uint8_t *field_value, size_t field_value_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); 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; 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, void nghttp2_option_set_user_recv_extension_type(nghttp2_option *option,
uint8_t type) { uint8_t type) {
if (type < 10) { 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->opt_set_mask |= NGHTTP2_OPT_USER_RECV_EXT_TYPES;
option->user_recv_ext_types[type / 8] = set_ext_type(option->user_recv_ext_types, type);
(uint8_t)(option->user_recv_ext_types[type / 8] | (1 << (type & 0x7))); }
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) { 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_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_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; } nghttp2_option_flag;
/** /**
@ -101,6 +102,10 @@ struct nghttp2_option {
* NGHTTP2_OPT_USER_RECV_EXT_TYPES * NGHTTP2_OPT_USER_RECV_EXT_TYPES
*/ */
uint8_t user_recv_ext_types[32]; uint8_t user_recv_ext_types[32];
/**
* NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES
*/
uint8_t builtin_recv_ext_types[32];
}; };
#endif /* NGHTTP2_OPTION_H */ #endif /* NGHTTP2_OPTION_H */

View File

@ -90,6 +90,8 @@ void nghttp2_outbound_item_free(nghttp2_outbound_item *item, nghttp2_mem *mem) {
assert(0); assert(0);
break; 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; 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, static int session_call_error_callback(nghttp2_session *session,
const char *fmt, ...) { const char *fmt, ...) {
size_t bufsize; size_t bufsize;
@ -316,7 +320,18 @@ static void session_inbound_frame_reset(nghttp2_session *session) {
break; break;
default: default:
/* extension frame */ /* extension frame */
if (check_ext_type_set(session->user_recv_ext_types,
iframe->frame.hd.type)) {
nghttp2_frame_extension_free(&iframe->frame.ext); 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; break;
} }
@ -332,6 +347,8 @@ static void session_inbound_frame_reset(nghttp2_session *session) {
nghttp2_buf_free(&iframe->lbuf, mem); nghttp2_buf_free(&iframe->lbuf, mem);
nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0); nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0);
iframe->raw_lbuf = NULL;
iframe->niv = 0; iframe->niv = 0;
iframe->payloadleft = 0; iframe->payloadleft = 0;
iframe->padlen = 0; iframe->padlen = 0;
@ -474,6 +491,12 @@ static int session_new(nghttp2_session **session_ptr,
sizeof((*session_ptr)->user_recv_ext_types)); 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) && if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_PING_ACK) &&
option->no_auto_ping_ack) { option->no_auto_ping_ack) {
(*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_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); 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) { static int session_process_extension_frame(nghttp2_session *session) {
int rv; int rv;
nghttp2_inbound_frame *iframe = &session->iframe; nghttp2_inbound_frame *iframe = &session->iframe;
@ -5480,11 +5541,11 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
break; break;
default: default:
DEBUGF(fprintf(stderr, "recv: unknown frame\n")); DEBUGF(fprintf(stderr, "recv: extension frame\n"));
if (!session->callbacks.unpack_extension_callback || if (check_ext_type_set(session->user_recv_ext_types,
(session->user_recv_ext_types[iframe->frame.hd.type / 8] & iframe->frame.hd.type)) {
(1 << (iframe->frame.hd.type & 0x7))) == 0) { if (!session->callbacks.unpack_extension_callback) {
/* Silently ignore unknown frame type. */ /* Silently ignore unknown frame type. */
busy = 1; 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; iframe->state = NGHTTP2_IB_READ_EXTENSION_PAYLOAD;
break; 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) { 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); session_inbound_frame_reset(session);
break; 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: default:
/* This is unknown frame */ /* This is unknown frame */
session_inbound_frame_reset(session); 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); 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; break;
} }

View File

@ -107,6 +107,7 @@ typedef enum {
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_ALTSVC_PAYLOAD,
NGHTTP2_IB_READ_EXTENSION_PAYLOAD NGHTTP2_IB_READ_EXTENSION_PAYLOAD
} nghttp2_inbound_state; } nghttp2_inbound_state;
@ -313,6 +314,7 @@ struct nghttp2_session {
bit is set, it indicates that incoming frame with that type is bit is set, it indicates that incoming frame with that type is
passed to user defined callbacks, otherwise they are ignored. */ passed to user defined callbacks, otherwise they are ignored. */
uint8_t user_recv_ext_types[32]; uint8_t user_recv_ext_types[32];
uint8_t builtin_recv_ext_types[32];
}; };
/* Struct used when updating initial window size of each active /* 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, int nghttp2_session_on_window_update_received(nghttp2_session *session,
nghttp2_frame *frame); 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 * Called when DATA is received, assuming |frame| is properly
* initialized. * 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); rv = nghttp2_session_add_item(session, item);
if (rv != 0) { if (rv != 0) {
nghttp2_frame_altsvc_free(&frame->ext, mem); nghttp2_frame_altsvc_free(&frame->ext, mem);
nghttp2_mem_free(mem, frame->ext.payload);
nghttp2_mem_free(mem, item); nghttp2_mem_free(mem, item);
return rv; return rv;

View File

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