altsvc: Add ALTSVC frame support
This commit is contained in:
parent
4a6fc6cede
commit
ecabef2dc7
|
@ -591,7 +591,11 @@ typedef enum {
|
||||||
* callbacks because the library processes this frame type and its
|
* callbacks because the library processes this frame type and its
|
||||||
* preceding HEADERS/PUSH_PROMISE as a single frame.
|
* preceding HEADERS/PUSH_PROMISE as a single frame.
|
||||||
*/
|
*/
|
||||||
NGHTTP2_CONTINUATION = 0x09
|
NGHTTP2_CONTINUATION = 0x09,
|
||||||
|
/**
|
||||||
|
* The ALTSVC frame.
|
||||||
|
*/
|
||||||
|
NGHTTP2_ALTSVC = 0x0a
|
||||||
} nghttp2_frame_type;
|
} nghttp2_frame_type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4113,6 +4117,32 @@ NGHTTP2_EXTERN int nghttp2_submit_extension(nghttp2_session *session,
|
||||||
uint8_t type, uint8_t flags,
|
uint8_t type, uint8_t flags,
|
||||||
int32_t stream_id, void *payload);
|
int32_t stream_id, void *payload);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* :enum:`NGHTTP2_ALTSVC`, ``nghttp2_extension.payload`` will point to
|
||||||
|
* this struct.
|
||||||
|
*
|
||||||
|
* It has the following members:
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint8_t *origin;
|
||||||
|
size_t origin_len;
|
||||||
|
uint8_t *field_value;
|
||||||
|
size_t field_value_len;
|
||||||
|
} nghttp2_ext_altsvc;
|
||||||
|
|
||||||
|
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
|
* @function
|
||||||
*
|
*
|
||||||
|
|
|
@ -193,6 +193,31 @@ void nghttp2_frame_extension_init(nghttp2_extension *frame, uint8_t type,
|
||||||
|
|
||||||
void nghttp2_frame_extension_free(nghttp2_extension *frame _U_) {}
|
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);
|
||||||
|
nghttp2_mem_free(mem, altsvc);
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
@ -668,6 +693,36 @@ void nghttp2_frame_unpack_window_update_payload(nghttp2_window_update *frame,
|
||||||
nghttp2_get_uint32(payload) & NGHTTP2_WINDOW_SIZE_INCREMENT_MASK;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
|
@ -367,6 +367,17 @@ void nghttp2_frame_unpack_window_update_payload(nghttp2_window_update *frame,
|
||||||
const uint8_t *payload,
|
const uint8_t *payload,
|
||||||
size_t payloadlen);
|
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);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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
|
||||||
|
@ -445,6 +456,12 @@ 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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
void nghttp2_frame_altsvc_free(nghttp2_extension *frame, nghttp2_mem *mem);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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
|
||||||
|
|
|
@ -72,9 +72,25 @@ 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:
|
default: {
|
||||||
nghttp2_frame_extension_free(&frame->ext);
|
nghttp2_ext_aux_data *aux_data;
|
||||||
break;
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,11 +87,19 @@ typedef struct {
|
||||||
uint8_t flags;
|
uint8_t flags;
|
||||||
} nghttp2_goaway_aux_data;
|
} 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 */
|
/* Additional data which cannot be stored in nghttp2_frame struct */
|
||||||
typedef union {
|
typedef union {
|
||||||
nghttp2_data_aux_data data;
|
nghttp2_data_aux_data data;
|
||||||
nghttp2_headers_aux_data headers;
|
nghttp2_headers_aux_data headers;
|
||||||
nghttp2_goaway_aux_data goaway;
|
nghttp2_goaway_aux_data goaway;
|
||||||
|
nghttp2_ext_aux_data ext;
|
||||||
} nghttp2_aux_data;
|
} nghttp2_aux_data;
|
||||||
|
|
||||||
struct nghttp2_outbound_item;
|
struct nghttp2_outbound_item;
|
||||||
|
|
|
@ -1664,6 +1664,26 @@ static int session_predicate_window_update_send(nghttp2_session *session,
|
||||||
return 0;
|
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
|
/* Take into account settings max frame size and both connection-level
|
||||||
flow control here */
|
flow control here */
|
||||||
static ssize_t
|
static ssize_t
|
||||||
|
@ -2103,19 +2123,45 @@ static int session_prep_frame(nghttp2_session *session,
|
||||||
/* We never handle CONTINUATION here. */
|
/* We never handle CONTINUATION here. */
|
||||||
assert(0);
|
assert(0);
|
||||||
break;
|
break;
|
||||||
default:
|
default: {
|
||||||
|
nghttp2_ext_aux_data *aux_data;
|
||||||
|
|
||||||
/* extension frame */
|
/* 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);
|
switch (frame->hd.type) {
|
||||||
if (rv != 0) {
|
case NGHTTP2_ALTSVC:
|
||||||
return rv;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
size_t next_readmax;
|
size_t next_readmax;
|
||||||
|
|
|
@ -410,6 +410,78 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags,
|
||||||
return 0;
|
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 (2 + origin_len + field_value_len > NGHTTP2_MAX_PAYLOADLEN) {
|
||||||
|
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;
|
||||||
|
p = nghttp2_cpymem(p, origin, origin_len);
|
||||||
|
*p++ = '\0';
|
||||||
|
|
||||||
|
field_value_copy = p;
|
||||||
|
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 = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
|
||||||
|
if (item == NULL) {
|
||||||
|
rv = NGHTTP2_ERR_NOMEM;
|
||||||
|
goto fail_altsvc_malloc;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 0;
|
||||||
|
|
||||||
|
fail_altsvc_malloc:
|
||||||
|
free(item);
|
||||||
|
fail_item_malloc:
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
static uint8_t set_request_flags(const nghttp2_priority_spec *pri_spec,
|
static uint8_t set_request_flags(const nghttp2_priority_spec *pri_spec,
|
||||||
const nghttp2_data_provider *data_prd) {
|
const nghttp2_data_provider *data_prd) {
|
||||||
uint8_t flags = NGHTTP2_FLAG_NONE;
|
uint8_t flags = NGHTTP2_FLAG_NONE;
|
||||||
|
|
Loading…
Reference in New Issue