diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 8cd00ca6..83d4173a 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -591,7 +591,11 @@ 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. + */ + NGHTTP2_ALTSVC = 0x0a } nghttp2_frame_type; /** @@ -4113,6 +4117,32 @@ 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 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 * diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index 3d057847..879c07e6 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -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_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) { if (flags & NGHTTP2_FLAG_PRIORITY) { 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; } +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, size_t niv, nghttp2_mem *mem) { nghttp2_settings_entry *iv_copy; diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h index fa0eb452..69feb73b 100644 --- a/lib/nghttp2_frame.h +++ b/lib/nghttp2_frame.h @@ -367,6 +367,17 @@ 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); + /* * Initializes HEADERS frame |frame| with given values. |frame| takes * 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_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 * padding length is given in the |padlen|. The returned value does diff --git a/lib/nghttp2_outbound_item.c b/lib/nghttp2_outbound_item.c index 886f330c..1633cc36 100644 --- a/lib/nghttp2_outbound_item.c +++ b/lib/nghttp2_outbound_item.c @@ -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; + } + } } } diff --git a/lib/nghttp2_outbound_item.h b/lib/nghttp2_outbound_item.h index f1daeb1c..ce458a3a 100644 --- a/lib/nghttp2_outbound_item.h +++ b/lib/nghttp2_outbound_item.h @@ -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; diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 84845abf..dd63f6f0 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -1664,6 +1664,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 +2123,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; diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c index 9c11e210..fc0663ce 100644 --- a/lib/nghttp2_submit.c +++ b/lib/nghttp2_submit.c @@ -410,6 +410,78 @@ 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 (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, const nghttp2_data_provider *data_prd) { uint8_t flags = NGHTTP2_FLAG_NONE;