diff options
Diffstat (limited to 'deps/node/deps/nghttp2/lib/nghttp2_session.c')
-rw-r--r-- | deps/node/deps/nghttp2/lib/nghttp2_session.c | 7769 |
1 files changed, 0 insertions, 7769 deletions
diff --git a/deps/node/deps/nghttp2/lib/nghttp2_session.c b/deps/node/deps/nghttp2/lib/nghttp2_session.c deleted file mode 100644 index ef4932af..00000000 --- a/deps/node/deps/nghttp2/lib/nghttp2_session.c +++ /dev/null @@ -1,7769 +0,0 @@ -/* - * nghttp2 - HTTP/2 C Library - * - * Copyright (c) 2012 Tatsuhiro Tsujikawa - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "nghttp2_session.h" - -#include <string.h> -#include <stddef.h> -#include <stdio.h> -#include <assert.h> -#include <stdarg.h> - -#include "nghttp2_helper.h" -#include "nghttp2_net.h" -#include "nghttp2_priority_spec.h" -#include "nghttp2_option.h" -#include "nghttp2_http.h" -#include "nghttp2_pq.h" -#include "nghttp2_debug.h" - -/* - * Returns non-zero if the number of outgoing opened streams is larger - * than or equal to - * remote_settings.max_concurrent_streams. - */ -static int -session_is_outgoing_concurrent_streams_max(nghttp2_session *session) { - return session->remote_settings.max_concurrent_streams <= - session->num_outgoing_streams; -} - -/* - * Returns non-zero if the number of incoming opened streams is larger - * than or equal to - * local_settings.max_concurrent_streams. - */ -static int -session_is_incoming_concurrent_streams_max(nghttp2_session *session) { - return session->local_settings.max_concurrent_streams <= - session->num_incoming_streams; -} - -/* - * Returns non-zero if the number of incoming opened streams is larger - * than or equal to - * session->pending_local_max_concurrent_stream. - */ -static int -session_is_incoming_concurrent_streams_pending_max(nghttp2_session *session) { - return session->pending_local_max_concurrent_stream <= - session->num_incoming_streams; -} - -/* - * Returns non-zero if |lib_error| is non-fatal error. - */ -static int is_non_fatal(int lib_error_code) { - return lib_error_code < 0 && lib_error_code > NGHTTP2_ERR_FATAL; -} - -int nghttp2_is_fatal(int lib_error_code) { - return lib_error_code < NGHTTP2_ERR_FATAL; -} - -static int session_enforce_http_messaging(nghttp2_session *session) { - return (session->opt_flags & NGHTTP2_OPTMASK_NO_HTTP_MESSAGING) == 0; -} - -/* - * Returns nonzero if |frame| is trailer headers. - */ -static int session_trailer_headers(nghttp2_session *session, - nghttp2_stream *stream, - nghttp2_frame *frame) { - if (!stream || frame->hd.type != NGHTTP2_HEADERS) { - return 0; - } - if (session->server) { - return frame->headers.cat == NGHTTP2_HCAT_HEADERS; - } - - return frame->headers.cat == NGHTTP2_HCAT_HEADERS && - (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) == 0; -} - -/* Returns nonzero if the |stream| is in reserved(remote) state */ -static int state_reserved_remote(nghttp2_session *session, - nghttp2_stream *stream) { - return stream->state == NGHTTP2_STREAM_RESERVED && - !nghttp2_session_is_my_stream_id(session, stream->stream_id); -} - -/* Returns nonzero if the |stream| is in reserved(local) state */ -static int state_reserved_local(nghttp2_session *session, - nghttp2_stream *stream) { - return stream->state == NGHTTP2_STREAM_RESERVED && - nghttp2_session_is_my_stream_id(session, stream->stream_id); -} - -/* - * Checks whether received stream_id is valid. This function returns - * 1 if it succeeds, or 0. - */ -static int session_is_new_peer_stream_id(nghttp2_session *session, - int32_t stream_id) { - return stream_id != 0 && - !nghttp2_session_is_my_stream_id(session, stream_id) && - session->last_recv_stream_id < stream_id; -} - -static int session_detect_idle_stream(nghttp2_session *session, - int32_t stream_id) { - /* Assume that stream object with stream_id does not exist */ - if (nghttp2_session_is_my_stream_id(session, stream_id)) { - if (session->last_sent_stream_id < stream_id) { - return 1; - } - return 0; - } - if (session_is_new_peer_stream_id(session, stream_id)) { - return 1; - } - 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, - int lib_error_code, const char *fmt, - ...) { - size_t bufsize; - va_list ap; - char *buf; - int rv; - nghttp2_mem *mem; - - if (!session->callbacks.error_callback && - !session->callbacks.error_callback2) { - return 0; - } - - mem = &session->mem; - - va_start(ap, fmt); - rv = vsnprintf(NULL, 0, fmt, ap); - va_end(ap); - - if (rv < 0) { - return NGHTTP2_ERR_NOMEM; - } - - bufsize = (size_t)(rv + 1); - - buf = nghttp2_mem_malloc(mem, bufsize); - if (buf == NULL) { - return NGHTTP2_ERR_NOMEM; - } - - va_start(ap, fmt); - rv = vsnprintf(buf, bufsize, fmt, ap); - va_end(ap); - - if (rv < 0) { - nghttp2_mem_free(mem, buf); - /* vsnprintf may return error because of various things we can - imagine, but typically we don't want to drop session just for - debug callback. */ - DEBUGF("error_callback: vsnprintf failed. The template was %s\n", fmt); - return 0; - } - - if (session->callbacks.error_callback2) { - rv = session->callbacks.error_callback2(session, lib_error_code, buf, - (size_t)rv, session->user_data); - } else { - rv = session->callbacks.error_callback(session, buf, (size_t)rv, - session->user_data); - } - - nghttp2_mem_free(mem, buf); - - if (rv != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static int session_terminate_session(nghttp2_session *session, - int32_t last_stream_id, - uint32_t error_code, const char *reason) { - int rv; - const uint8_t *debug_data; - size_t debug_datalen; - - if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) { - return 0; - } - - /* Ignore all incoming frames because we are going to tear down the - session. */ - session->iframe.state = NGHTTP2_IB_IGN_ALL; - - if (reason == NULL) { - debug_data = NULL; - debug_datalen = 0; - } else { - debug_data = (const uint8_t *)reason; - debug_datalen = strlen(reason); - } - - rv = nghttp2_session_add_goaway(session, last_stream_id, error_code, - debug_data, debug_datalen, - NGHTTP2_GOAWAY_AUX_TERM_ON_SEND); - - if (rv != 0) { - return rv; - } - - session->goaway_flags |= NGHTTP2_GOAWAY_TERM_ON_SEND; - - return 0; -} - -int nghttp2_session_terminate_session(nghttp2_session *session, - uint32_t error_code) { - return session_terminate_session(session, session->last_proc_stream_id, - error_code, NULL); -} - -int nghttp2_session_terminate_session2(nghttp2_session *session, - int32_t last_stream_id, - uint32_t error_code) { - return session_terminate_session(session, last_stream_id, error_code, NULL); -} - -int nghttp2_session_terminate_session_with_reason(nghttp2_session *session, - uint32_t error_code, - const char *reason) { - return session_terminate_session(session, session->last_proc_stream_id, - error_code, reason); -} - -int nghttp2_session_is_my_stream_id(nghttp2_session *session, - int32_t stream_id) { - int rem; - if (stream_id == 0) { - return 0; - } - rem = stream_id & 0x1; - if (session->server) { - return rem == 0; - } - return rem == 1; -} - -nghttp2_stream *nghttp2_session_get_stream(nghttp2_session *session, - int32_t stream_id) { - nghttp2_stream *stream; - - stream = (nghttp2_stream *)nghttp2_map_find(&session->streams, stream_id); - - if (stream == NULL || (stream->flags & NGHTTP2_STREAM_FLAG_CLOSED) || - stream->state == NGHTTP2_STREAM_IDLE) { - return NULL; - } - - return stream; -} - -nghttp2_stream *nghttp2_session_get_stream_raw(nghttp2_session *session, - int32_t stream_id) { - return (nghttp2_stream *)nghttp2_map_find(&session->streams, stream_id); -} - -static void session_inbound_frame_reset(nghttp2_session *session) { - nghttp2_inbound_frame *iframe = &session->iframe; - nghttp2_mem *mem = &session->mem; - /* A bit risky code, since if this function is called from - nghttp2_session_new(), we rely on the fact that - iframe->frame.hd.type is 0, so that no free is performed. */ - switch (iframe->frame.hd.type) { - case NGHTTP2_DATA: - break; - case NGHTTP2_HEADERS: - nghttp2_frame_headers_free(&iframe->frame.headers, mem); - break; - case NGHTTP2_PRIORITY: - nghttp2_frame_priority_free(&iframe->frame.priority); - break; - case NGHTTP2_RST_STREAM: - nghttp2_frame_rst_stream_free(&iframe->frame.rst_stream); - break; - case NGHTTP2_SETTINGS: - nghttp2_frame_settings_free(&iframe->frame.settings, mem); - - nghttp2_mem_free(mem, iframe->iv); - - iframe->iv = NULL; - iframe->niv = 0; - iframe->max_niv = 0; - - break; - case NGHTTP2_PUSH_PROMISE: - nghttp2_frame_push_promise_free(&iframe->frame.push_promise, mem); - break; - case NGHTTP2_PING: - nghttp2_frame_ping_free(&iframe->frame.ping); - break; - case NGHTTP2_GOAWAY: - nghttp2_frame_goaway_free(&iframe->frame.goaway, mem); - break; - case NGHTTP2_WINDOW_UPDATE: - nghttp2_frame_window_update_free(&iframe->frame.window_update); - 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 { - 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; - case NGHTTP2_ORIGIN: - if ((session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ORIGIN) == 0) { - break; - } - nghttp2_frame_origin_free(&iframe->frame.ext, mem); - break; - } - } - - break; - } - - memset(&iframe->frame, 0, sizeof(nghttp2_frame)); - memset(&iframe->ext_frame_payload, 0, sizeof(nghttp2_ext_frame_payload)); - - iframe->state = NGHTTP2_IB_READ_HEAD; - - nghttp2_buf_wrap_init(&iframe->sbuf, iframe->raw_sbuf, - sizeof(iframe->raw_sbuf)); - iframe->sbuf.mark += NGHTTP2_FRAME_HDLEN; - - nghttp2_buf_free(&iframe->lbuf, mem); - nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0); - - iframe->raw_lbuf = NULL; - - iframe->payloadleft = 0; - iframe->padlen = 0; -} - -static void init_settings(nghttp2_settings_storage *settings) { - settings->header_table_size = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE; - settings->enable_push = 1; - settings->max_concurrent_streams = NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS; - settings->initial_window_size = NGHTTP2_INITIAL_WINDOW_SIZE; - settings->max_frame_size = NGHTTP2_MAX_FRAME_SIZE_MIN; - settings->max_header_list_size = UINT32_MAX; -} - -static void active_outbound_item_reset(nghttp2_active_outbound_item *aob, - nghttp2_mem *mem) { - DEBUGF("send: reset nghttp2_active_outbound_item\n"); - DEBUGF("send: aob->item = %p\n", aob->item); - nghttp2_outbound_item_free(aob->item, mem); - nghttp2_mem_free(mem, aob->item); - aob->item = NULL; - nghttp2_bufs_reset(&aob->framebufs); - aob->state = NGHTTP2_OB_POP_ITEM; -} - -int nghttp2_enable_strict_preface = 1; - -static int session_new(nghttp2_session **session_ptr, - const nghttp2_session_callbacks *callbacks, - void *user_data, int server, - const nghttp2_option *option, nghttp2_mem *mem) { - int rv; - size_t nbuffer; - size_t max_deflate_dynamic_table_size = - NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE; - - if (mem == NULL) { - mem = nghttp2_mem_default(); - } - - *session_ptr = nghttp2_mem_calloc(mem, 1, sizeof(nghttp2_session)); - if (*session_ptr == NULL) { - rv = NGHTTP2_ERR_NOMEM; - goto fail_session; - } - - (*session_ptr)->mem = *mem; - mem = &(*session_ptr)->mem; - - /* next_stream_id is initialized in either - nghttp2_session_client_new2 or nghttp2_session_server_new2 */ - - nghttp2_stream_init(&(*session_ptr)->root, 0, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_STREAM_IDLE, NGHTTP2_DEFAULT_WEIGHT, 0, 0, NULL, - mem); - - (*session_ptr)->remote_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE; - (*session_ptr)->recv_window_size = 0; - (*session_ptr)->consumed_size = 0; - (*session_ptr)->recv_reduction = 0; - (*session_ptr)->local_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE; - - (*session_ptr)->goaway_flags = NGHTTP2_GOAWAY_NONE; - (*session_ptr)->local_last_stream_id = (1u << 31) - 1; - (*session_ptr)->remote_last_stream_id = (1u << 31) - 1; - - (*session_ptr)->pending_local_max_concurrent_stream = - NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS; - (*session_ptr)->pending_enable_push = 1; - - if (server) { - (*session_ptr)->server = 1; - } - - init_settings(&(*session_ptr)->remote_settings); - init_settings(&(*session_ptr)->local_settings); - - (*session_ptr)->max_incoming_reserved_streams = - NGHTTP2_MAX_INCOMING_RESERVED_STREAMS; - - /* Limit max outgoing concurrent streams to sensible value */ - (*session_ptr)->remote_settings.max_concurrent_streams = 100; - - (*session_ptr)->max_send_header_block_length = NGHTTP2_MAX_HEADERSLEN; - - if (option) { - if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) && - option->no_auto_window_update) { - - (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE; - } - - if (option->opt_set_mask & NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS) { - - (*session_ptr)->remote_settings.max_concurrent_streams = - option->peer_max_concurrent_streams; - } - - if (option->opt_set_mask & NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS) { - - (*session_ptr)->max_incoming_reserved_streams = - option->max_reserved_remote_streams; - } - - if ((option->opt_set_mask & NGHTTP2_OPT_NO_RECV_CLIENT_MAGIC) && - option->no_recv_client_magic) { - - (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_RECV_CLIENT_MAGIC; - } - - if ((option->opt_set_mask & NGHTTP2_OPT_NO_HTTP_MESSAGING) && - option->no_http_messaging) { - - (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_HTTP_MESSAGING; - } - - if (option->opt_set_mask & NGHTTP2_OPT_USER_RECV_EXT_TYPES) { - memcpy((*session_ptr)->user_recv_ext_types, option->user_recv_ext_types, - 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; - } - - if (option->opt_set_mask & NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH) { - (*session_ptr)->max_send_header_block_length = - option->max_send_header_block_length; - } - - if (option->opt_set_mask & NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE) { - max_deflate_dynamic_table_size = option->max_deflate_dynamic_table_size; - } - - if ((option->opt_set_mask & NGHTTP2_OPT_NO_CLOSED_STREAMS) && - option->no_closed_streams) { - (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_CLOSED_STREAMS; - } - } - - rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater, - max_deflate_dynamic_table_size, mem); - if (rv != 0) { - goto fail_hd_deflater; - } - rv = nghttp2_hd_inflate_init(&(*session_ptr)->hd_inflater, mem); - if (rv != 0) { - goto fail_hd_inflater; - } - rv = nghttp2_map_init(&(*session_ptr)->streams, mem); - if (rv != 0) { - goto fail_map; - } - - nbuffer = ((*session_ptr)->max_send_header_block_length + - NGHTTP2_FRAMEBUF_CHUNKLEN - 1) / - NGHTTP2_FRAMEBUF_CHUNKLEN; - - if (nbuffer == 0) { - nbuffer = 1; - } - - /* 1 for Pad Field. */ - rv = nghttp2_bufs_init3(&(*session_ptr)->aob.framebufs, - NGHTTP2_FRAMEBUF_CHUNKLEN, nbuffer, 1, - NGHTTP2_FRAME_HDLEN + 1, mem); - if (rv != 0) { - goto fail_aob_framebuf; - } - - active_outbound_item_reset(&(*session_ptr)->aob, mem); - - (*session_ptr)->callbacks = *callbacks; - (*session_ptr)->user_data = user_data; - - session_inbound_frame_reset(*session_ptr); - - if (nghttp2_enable_strict_preface) { - nghttp2_inbound_frame *iframe = &(*session_ptr)->iframe; - - if (server && ((*session_ptr)->opt_flags & - NGHTTP2_OPTMASK_NO_RECV_CLIENT_MAGIC) == 0) { - iframe->state = NGHTTP2_IB_READ_CLIENT_MAGIC; - iframe->payloadleft = NGHTTP2_CLIENT_MAGIC_LEN; - } else { - iframe->state = NGHTTP2_IB_READ_FIRST_SETTINGS; - } - - if (!server) { - (*session_ptr)->aob.state = NGHTTP2_OB_SEND_CLIENT_MAGIC; - nghttp2_bufs_add(&(*session_ptr)->aob.framebufs, NGHTTP2_CLIENT_MAGIC, - NGHTTP2_CLIENT_MAGIC_LEN); - } - } - - return 0; - -fail_aob_framebuf: - nghttp2_map_free(&(*session_ptr)->streams); -fail_map: - nghttp2_hd_inflate_free(&(*session_ptr)->hd_inflater); -fail_hd_inflater: - nghttp2_hd_deflate_free(&(*session_ptr)->hd_deflater); -fail_hd_deflater: - nghttp2_mem_free(mem, *session_ptr); -fail_session: - return rv; -} - -int nghttp2_session_client_new(nghttp2_session **session_ptr, - const nghttp2_session_callbacks *callbacks, - void *user_data) { - return nghttp2_session_client_new3(session_ptr, callbacks, user_data, NULL, - NULL); -} - -int nghttp2_session_client_new2(nghttp2_session **session_ptr, - const nghttp2_session_callbacks *callbacks, - void *user_data, const nghttp2_option *option) { - return nghttp2_session_client_new3(session_ptr, callbacks, user_data, option, - NULL); -} - -int nghttp2_session_client_new3(nghttp2_session **session_ptr, - const nghttp2_session_callbacks *callbacks, - void *user_data, const nghttp2_option *option, - nghttp2_mem *mem) { - int rv; - nghttp2_session *session; - - rv = session_new(&session, callbacks, user_data, 0, option, mem); - - if (rv != 0) { - return rv; - } - /* IDs for use in client */ - session->next_stream_id = 1; - - *session_ptr = session; - - return 0; -} - -int nghttp2_session_server_new(nghttp2_session **session_ptr, - const nghttp2_session_callbacks *callbacks, - void *user_data) { - return nghttp2_session_server_new3(session_ptr, callbacks, user_data, NULL, - NULL); -} - -int nghttp2_session_server_new2(nghttp2_session **session_ptr, - const nghttp2_session_callbacks *callbacks, - void *user_data, const nghttp2_option *option) { - return nghttp2_session_server_new3(session_ptr, callbacks, user_data, option, - NULL); -} - -int nghttp2_session_server_new3(nghttp2_session **session_ptr, - const nghttp2_session_callbacks *callbacks, - void *user_data, const nghttp2_option *option, - nghttp2_mem *mem) { - int rv; - nghttp2_session *session; - - rv = session_new(&session, callbacks, user_data, 1, option, mem); - - if (rv != 0) { - return rv; - } - /* IDs for use in client */ - session->next_stream_id = 2; - - *session_ptr = session; - - return 0; -} - -static int free_streams(nghttp2_map_entry *entry, void *ptr) { - nghttp2_session *session; - nghttp2_stream *stream; - nghttp2_outbound_item *item; - nghttp2_mem *mem; - - session = (nghttp2_session *)ptr; - mem = &session->mem; - stream = (nghttp2_stream *)entry; - item = stream->item; - - if (item && !item->queued && item != session->aob.item) { - nghttp2_outbound_item_free(item, mem); - nghttp2_mem_free(mem, item); - } - - nghttp2_stream_free(stream); - nghttp2_mem_free(mem, stream); - - return 0; -} - -static void ob_q_free(nghttp2_outbound_queue *q, nghttp2_mem *mem) { - nghttp2_outbound_item *item, *next; - for (item = q->head; item;) { - next = item->qnext; - nghttp2_outbound_item_free(item, mem); - nghttp2_mem_free(mem, item); - item = next; - } -} - -static int inflight_settings_new(nghttp2_inflight_settings **settings_ptr, - const nghttp2_settings_entry *iv, size_t niv, - nghttp2_mem *mem) { - *settings_ptr = nghttp2_mem_malloc(mem, sizeof(nghttp2_inflight_settings)); - if (!*settings_ptr) { - return NGHTTP2_ERR_NOMEM; - } - - if (niv > 0) { - (*settings_ptr)->iv = nghttp2_frame_iv_copy(iv, niv, mem); - if (!(*settings_ptr)->iv) { - nghttp2_mem_free(mem, *settings_ptr); - return NGHTTP2_ERR_NOMEM; - } - } else { - (*settings_ptr)->iv = NULL; - } - - (*settings_ptr)->niv = niv; - (*settings_ptr)->next = NULL; - - return 0; -} - -static void inflight_settings_del(nghttp2_inflight_settings *settings, - nghttp2_mem *mem) { - if (!settings) { - return; - } - - nghttp2_mem_free(mem, settings->iv); - nghttp2_mem_free(mem, settings); -} - -void nghttp2_session_del(nghttp2_session *session) { - nghttp2_mem *mem; - nghttp2_inflight_settings *settings; - - if (session == NULL) { - return; - } - - mem = &session->mem; - - for (settings = session->inflight_settings_head; settings;) { - nghttp2_inflight_settings *next = settings->next; - inflight_settings_del(settings, mem); - settings = next; - } - - nghttp2_stream_free(&session->root); - - /* Have to free streams first, so that we can check - stream->item->queued */ - nghttp2_map_each_free(&session->streams, free_streams, session); - nghttp2_map_free(&session->streams); - - ob_q_free(&session->ob_urgent, mem); - ob_q_free(&session->ob_reg, mem); - ob_q_free(&session->ob_syn, mem); - - active_outbound_item_reset(&session->aob, mem); - session_inbound_frame_reset(session); - nghttp2_hd_deflate_free(&session->hd_deflater); - nghttp2_hd_inflate_free(&session->hd_inflater); - nghttp2_bufs_free(&session->aob.framebufs); - nghttp2_mem_free(mem, session); -} - -int nghttp2_session_reprioritize_stream( - nghttp2_session *session, nghttp2_stream *stream, - const nghttp2_priority_spec *pri_spec_in) { - int rv; - nghttp2_stream *dep_stream = NULL; - nghttp2_priority_spec pri_spec_default; - const nghttp2_priority_spec *pri_spec = pri_spec_in; - - assert(pri_spec->stream_id != stream->stream_id); - - if (!nghttp2_stream_in_dep_tree(stream)) { - return 0; - } - - if (pri_spec->stream_id != 0) { - dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id); - - if (!dep_stream && - session_detect_idle_stream(session, pri_spec->stream_id)) { - - nghttp2_priority_spec_default_init(&pri_spec_default); - - dep_stream = nghttp2_session_open_stream( - session, pri_spec->stream_id, NGHTTP2_FLAG_NONE, &pri_spec_default, - NGHTTP2_STREAM_IDLE, NULL); - - if (dep_stream == NULL) { - return NGHTTP2_ERR_NOMEM; - } - } else if (!dep_stream || !nghttp2_stream_in_dep_tree(dep_stream)) { - nghttp2_priority_spec_default_init(&pri_spec_default); - pri_spec = &pri_spec_default; - } - } - - if (pri_spec->stream_id == 0) { - dep_stream = &session->root; - } else if (nghttp2_stream_dep_find_ancestor(dep_stream, stream)) { - DEBUGF("stream: cycle detected, dep_stream(%p)=%d stream(%p)=%d\n", - dep_stream, dep_stream->stream_id, stream, stream->stream_id); - - nghttp2_stream_dep_remove_subtree(dep_stream); - rv = nghttp2_stream_dep_add_subtree(stream->dep_prev, dep_stream); - if (rv != 0) { - return rv; - } - } - - assert(dep_stream); - - if (dep_stream == stream->dep_prev && !pri_spec->exclusive) { - /* This is minor optimization when just weight is changed. */ - nghttp2_stream_change_weight(stream, pri_spec->weight); - - return 0; - } - - nghttp2_stream_dep_remove_subtree(stream); - - /* We have to update weight after removing stream from tree */ - stream->weight = pri_spec->weight; - - if (pri_spec->exclusive) { - rv = nghttp2_stream_dep_insert_subtree(dep_stream, stream); - } else { - rv = nghttp2_stream_dep_add_subtree(dep_stream, stream); - } - - if (rv != 0) { - return rv; - } - - return 0; -} - -int nghttp2_session_add_item(nghttp2_session *session, - nghttp2_outbound_item *item) { - /* TODO Return error if stream is not found for the frame requiring - stream presence. */ - int rv = 0; - nghttp2_stream *stream; - nghttp2_frame *frame; - - frame = &item->frame; - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - - switch (frame->hd.type) { - case NGHTTP2_DATA: - if (!stream) { - return NGHTTP2_ERR_STREAM_CLOSED; - } - - if (stream->item) { - return NGHTTP2_ERR_DATA_EXIST; - } - - rv = nghttp2_stream_attach_item(stream, item); - - if (rv != 0) { - return rv; - } - - return 0; - case NGHTTP2_HEADERS: - /* We push request HEADERS and push response HEADERS to - dedicated queue because their transmission is affected by - SETTINGS_MAX_CONCURRENT_STREAMS */ - /* TODO If 2 HEADERS are submitted for reserved stream, then - both of them are queued into ob_syn, which is not - desirable. */ - if (frame->headers.cat == NGHTTP2_HCAT_REQUEST || - (stream && stream->state == NGHTTP2_STREAM_RESERVED)) { - nghttp2_outbound_queue_push(&session->ob_syn, item); - item->queued = 1; - return 0; - ; - } - - nghttp2_outbound_queue_push(&session->ob_reg, item); - item->queued = 1; - return 0; - case NGHTTP2_SETTINGS: - case NGHTTP2_PING: - nghttp2_outbound_queue_push(&session->ob_urgent, item); - item->queued = 1; - return 0; - case NGHTTP2_RST_STREAM: - if (stream) { - stream->state = NGHTTP2_STREAM_CLOSING; - } - nghttp2_outbound_queue_push(&session->ob_reg, item); - item->queued = 1; - return 0; - case NGHTTP2_PUSH_PROMISE: { - nghttp2_headers_aux_data *aux_data; - nghttp2_priority_spec pri_spec; - - aux_data = &item->aux_data.headers; - - if (!stream) { - return NGHTTP2_ERR_STREAM_CLOSED; - } - - nghttp2_priority_spec_init(&pri_spec, stream->stream_id, - NGHTTP2_DEFAULT_WEIGHT, 0); - - if (!nghttp2_session_open_stream( - session, frame->push_promise.promised_stream_id, - NGHTTP2_STREAM_FLAG_NONE, &pri_spec, NGHTTP2_STREAM_RESERVED, - aux_data->stream_user_data)) { - return NGHTTP2_ERR_NOMEM; - } - - /* We don't have to call nghttp2_session_adjust_closed_stream() - here, since stream->stream_id is local stream_id, and it does - not affect closed stream count. */ - - nghttp2_outbound_queue_push(&session->ob_reg, item); - item->queued = 1; - - return 0; - } - case NGHTTP2_WINDOW_UPDATE: - if (stream) { - stream->window_update_queued = 1; - } else if (frame->hd.stream_id == 0) { - session->window_update_queued = 1; - } - nghttp2_outbound_queue_push(&session->ob_reg, item); - item->queued = 1; - return 0; - default: - nghttp2_outbound_queue_push(&session->ob_reg, item); - item->queued = 1; - return 0; - } -} - -int nghttp2_session_add_rst_stream(nghttp2_session *session, int32_t stream_id, - uint32_t error_code) { - int rv; - nghttp2_outbound_item *item; - nghttp2_frame *frame; - nghttp2_stream *stream; - nghttp2_mem *mem; - - mem = &session->mem; - stream = nghttp2_session_get_stream(session, stream_id); - if (stream && stream->state == NGHTTP2_STREAM_CLOSING) { - return 0; - } - - /* Cancel pending request HEADERS in ob_syn if this RST_STREAM - refers to that stream. */ - if (!session->server && nghttp2_session_is_my_stream_id(session, stream_id) && - nghttp2_outbound_queue_top(&session->ob_syn)) { - nghttp2_headers_aux_data *aux_data; - nghttp2_frame *headers_frame; - - headers_frame = &nghttp2_outbound_queue_top(&session->ob_syn)->frame; - assert(headers_frame->hd.type == NGHTTP2_HEADERS); - - if (headers_frame->hd.stream_id <= stream_id && - (uint32_t)stream_id < session->next_stream_id) { - - for (item = session->ob_syn.head; item; item = item->qnext) { - aux_data = &item->aux_data.headers; - - if (item->frame.hd.stream_id < stream_id) { - continue; - } - - /* stream_id in ob_syn queue must be strictly increasing. If - we found larger ID, then we can break here. */ - if (item->frame.hd.stream_id > stream_id || aux_data->canceled) { - break; - } - - aux_data->error_code = error_code; - aux_data->canceled = 1; - - return 0; - } - } - } - - item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); - if (item == NULL) { - return NGHTTP2_ERR_NOMEM; - } - - nghttp2_outbound_item_init(item); - - frame = &item->frame; - - nghttp2_frame_rst_stream_init(&frame->rst_stream, stream_id, error_code); - rv = nghttp2_session_add_item(session, item); - if (rv != 0) { - nghttp2_frame_rst_stream_free(&frame->rst_stream); - nghttp2_mem_free(mem, item); - return rv; - } - return 0; -} - -nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session, - int32_t stream_id, uint8_t flags, - nghttp2_priority_spec *pri_spec_in, - nghttp2_stream_state initial_state, - void *stream_user_data) { - int rv; - nghttp2_stream *stream; - nghttp2_stream *dep_stream = NULL; - int stream_alloc = 0; - nghttp2_priority_spec pri_spec_default; - nghttp2_priority_spec *pri_spec = pri_spec_in; - nghttp2_mem *mem; - - mem = &session->mem; - stream = nghttp2_session_get_stream_raw(session, stream_id); - - if (stream) { - assert(stream->state == NGHTTP2_STREAM_IDLE); - assert(nghttp2_stream_in_dep_tree(stream)); - nghttp2_session_detach_idle_stream(session, stream); - rv = nghttp2_stream_dep_remove(stream); - if (rv != 0) { - return NULL; - } - } else { - stream = nghttp2_mem_malloc(mem, sizeof(nghttp2_stream)); - if (stream == NULL) { - return NULL; - } - - stream_alloc = 1; - } - - if (pri_spec->stream_id != 0) { - dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id); - - if (!dep_stream && - session_detect_idle_stream(session, pri_spec->stream_id)) { - /* Depends on idle stream, which does not exist in memory. - Assign default priority for it. */ - nghttp2_priority_spec_default_init(&pri_spec_default); - - dep_stream = nghttp2_session_open_stream( - session, pri_spec->stream_id, NGHTTP2_FLAG_NONE, &pri_spec_default, - NGHTTP2_STREAM_IDLE, NULL); - - if (dep_stream == NULL) { - if (stream_alloc) { - nghttp2_mem_free(mem, stream); - } - - return NULL; - } - } else if (!dep_stream || !nghttp2_stream_in_dep_tree(dep_stream)) { - /* If dep_stream is not part of dependency tree, stream will get - default priority. This handles the case when - pri_spec->stream_id == stream_id. This happens because we - don't check pri_spec->stream_id against new stream ID in - nghttp2_submit_request. This also handles the case when idle - stream created by PRIORITY frame was opened. Somehow we - first remove the idle stream from dependency tree. This is - done to simplify code base, but ideally we should retain old - dependency. But I'm not sure this adds values. */ - nghttp2_priority_spec_default_init(&pri_spec_default); - pri_spec = &pri_spec_default; - } - } - - if (initial_state == NGHTTP2_STREAM_RESERVED) { - flags |= NGHTTP2_STREAM_FLAG_PUSH; - } - - if (stream_alloc) { - nghttp2_stream_init(stream, stream_id, flags, initial_state, - pri_spec->weight, - (int32_t)session->remote_settings.initial_window_size, - (int32_t)session->local_settings.initial_window_size, - stream_user_data, mem); - - rv = nghttp2_map_insert(&session->streams, &stream->map_entry); - if (rv != 0) { - nghttp2_stream_free(stream); - nghttp2_mem_free(mem, stream); - return NULL; - } - } else { - stream->flags = flags; - stream->state = initial_state; - stream->weight = pri_spec->weight; - stream->stream_user_data = stream_user_data; - } - - switch (initial_state) { - case NGHTTP2_STREAM_RESERVED: - if (nghttp2_session_is_my_stream_id(session, stream_id)) { - /* reserved (local) */ - nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); - } else { - /* reserved (remote) */ - nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); - ++session->num_incoming_reserved_streams; - } - /* Reserved stream does not count in the concurrent streams - limit. That is one of the DOS vector. */ - break; - case NGHTTP2_STREAM_IDLE: - /* Idle stream does not count toward the concurrent streams limit. - This is used as anchor node in dependency tree. */ - nghttp2_session_keep_idle_stream(session, stream); - break; - default: - if (nghttp2_session_is_my_stream_id(session, stream_id)) { - ++session->num_outgoing_streams; - } else { - ++session->num_incoming_streams; - } - } - - if (pri_spec->stream_id == 0) { - dep_stream = &session->root; - } - - assert(dep_stream); - - if (pri_spec->exclusive) { - rv = nghttp2_stream_dep_insert(dep_stream, stream); - if (rv != 0) { - return NULL; - } - } else { - nghttp2_stream_dep_add(dep_stream, stream); - } - - return stream; -} - -int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id, - uint32_t error_code) { - int rv; - nghttp2_stream *stream; - nghttp2_mem *mem; - int is_my_stream_id; - - mem = &session->mem; - stream = nghttp2_session_get_stream(session, stream_id); - - if (!stream) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - - DEBUGF("stream: stream(%p)=%d close\n", stream, stream->stream_id); - - if (stream->item) { - nghttp2_outbound_item *item; - - item = stream->item; - - rv = nghttp2_stream_detach_item(stream); - - if (rv != 0) { - return rv; - } - - /* If item is queued, it will be deleted when it is popped - (nghttp2_session_prep_frame() will fail). If session->aob.item - points to this item, let active_outbound_item_reset() - free the item. */ - if (!item->queued && item != session->aob.item) { - nghttp2_outbound_item_free(item, mem); - nghttp2_mem_free(mem, item); - } - } - - /* We call on_stream_close_callback even if stream->state is - NGHTTP2_STREAM_INITIAL. This will happen while sending request - HEADERS, a local endpoint receives RST_STREAM for that stream. It - may be PROTOCOL_ERROR, but without notifying stream closure will - hang the stream in a local endpoint. - */ - - if (session->callbacks.on_stream_close_callback) { - if (session->callbacks.on_stream_close_callback( - session, stream_id, error_code, session->user_data) != 0) { - - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } - - is_my_stream_id = nghttp2_session_is_my_stream_id(session, stream_id); - - /* pushed streams which is not opened yet is not counted toward max - concurrent limits */ - if ((stream->flags & NGHTTP2_STREAM_FLAG_PUSH)) { - if (!is_my_stream_id) { - --session->num_incoming_reserved_streams; - } - } else { - if (is_my_stream_id) { - --session->num_outgoing_streams; - } else { - --session->num_incoming_streams; - } - } - - /* Closes both directions just in case they are not closed yet */ - stream->flags |= NGHTTP2_STREAM_FLAG_CLOSED; - - if ((session->opt_flags & NGHTTP2_OPTMASK_NO_CLOSED_STREAMS) == 0 && - session->server && !is_my_stream_id && - nghttp2_stream_in_dep_tree(stream)) { - /* On server side, retain stream at most MAX_CONCURRENT_STREAMS - combined with the current active incoming streams to make - dependency tree work better. */ - nghttp2_session_keep_closed_stream(session, stream); - } else { - rv = nghttp2_session_destroy_stream(session, stream); - if (rv != 0) { - return rv; - } - } - - return 0; -} - -int nghttp2_session_destroy_stream(nghttp2_session *session, - nghttp2_stream *stream) { - nghttp2_mem *mem; - int rv; - - DEBUGF("stream: destroy closed stream(%p)=%d\n", stream, stream->stream_id); - - mem = &session->mem; - - if (nghttp2_stream_in_dep_tree(stream)) { - rv = nghttp2_stream_dep_remove(stream); - if (rv != 0) { - return rv; - } - } - - nghttp2_map_remove(&session->streams, stream->stream_id); - nghttp2_stream_free(stream); - nghttp2_mem_free(mem, stream); - - return 0; -} - -void nghttp2_session_keep_closed_stream(nghttp2_session *session, - nghttp2_stream *stream) { - DEBUGF("stream: keep closed stream(%p)=%d, state=%d\n", stream, - stream->stream_id, stream->state); - - if (session->closed_stream_tail) { - session->closed_stream_tail->closed_next = stream; - stream->closed_prev = session->closed_stream_tail; - } else { - session->closed_stream_head = stream; - } - session->closed_stream_tail = stream; - - ++session->num_closed_streams; -} - -void nghttp2_session_keep_idle_stream(nghttp2_session *session, - nghttp2_stream *stream) { - DEBUGF("stream: keep idle stream(%p)=%d, state=%d\n", stream, - stream->stream_id, stream->state); - - if (session->idle_stream_tail) { - session->idle_stream_tail->closed_next = stream; - stream->closed_prev = session->idle_stream_tail; - } else { - session->idle_stream_head = stream; - } - session->idle_stream_tail = stream; - - ++session->num_idle_streams; -} - -void nghttp2_session_detach_idle_stream(nghttp2_session *session, - nghttp2_stream *stream) { - nghttp2_stream *prev_stream, *next_stream; - - DEBUGF("stream: detach idle stream(%p)=%d, state=%d\n", stream, - stream->stream_id, stream->state); - - prev_stream = stream->closed_prev; - next_stream = stream->closed_next; - - if (prev_stream) { - prev_stream->closed_next = next_stream; - } else { - session->idle_stream_head = next_stream; - } - - if (next_stream) { - next_stream->closed_prev = prev_stream; - } else { - session->idle_stream_tail = prev_stream; - } - - stream->closed_prev = NULL; - stream->closed_next = NULL; - - --session->num_idle_streams; -} - -int nghttp2_session_adjust_closed_stream(nghttp2_session *session) { - size_t num_stream_max; - int rv; - - if (session->local_settings.max_concurrent_streams == - NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS) { - num_stream_max = session->pending_local_max_concurrent_stream; - } else { - num_stream_max = session->local_settings.max_concurrent_streams; - } - - DEBUGF("stream: adjusting kept closed streams num_closed_streams=%zu, " - "num_incoming_streams=%zu, max_concurrent_streams=%zu\n", - session->num_closed_streams, session->num_incoming_streams, - num_stream_max); - - while (session->num_closed_streams > 0 && - session->num_closed_streams + session->num_incoming_streams > - num_stream_max) { - nghttp2_stream *head_stream; - nghttp2_stream *next; - - head_stream = session->closed_stream_head; - - assert(head_stream); - - next = head_stream->closed_next; - - rv = nghttp2_session_destroy_stream(session, head_stream); - if (rv != 0) { - return rv; - } - - /* head_stream is now freed */ - - session->closed_stream_head = next; - - if (session->closed_stream_head) { - session->closed_stream_head->closed_prev = NULL; - } else { - session->closed_stream_tail = NULL; - } - - --session->num_closed_streams; - } - - return 0; -} - -int nghttp2_session_adjust_idle_stream(nghttp2_session *session) { - size_t max; - int rv; - - /* Make minimum number of idle streams 16, and maximum 100, which - are arbitrary chosen numbers. */ - max = nghttp2_min( - 100, nghttp2_max( - 16, nghttp2_min(session->local_settings.max_concurrent_streams, - session->pending_local_max_concurrent_stream))); - - DEBUGF("stream: adjusting kept idle streams num_idle_streams=%zu, max=%zu\n", - session->num_idle_streams, max); - - while (session->num_idle_streams > max) { - nghttp2_stream *head; - nghttp2_stream *next; - - head = session->idle_stream_head; - assert(head); - - next = head->closed_next; - - rv = nghttp2_session_destroy_stream(session, head); - if (rv != 0) { - return rv; - } - - /* head is now destroyed */ - - session->idle_stream_head = next; - - if (session->idle_stream_head) { - session->idle_stream_head->closed_prev = NULL; - } else { - session->idle_stream_tail = NULL; - } - - --session->num_idle_streams; - } - - return 0; -} - -/* - * Closes stream with stream ID |stream_id| if both transmission and - * reception of the stream were disallowed. The |error_code| indicates - * the reason of the closure. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_INVALID_ARGUMENT - * The stream is not found. - * NGHTTP2_ERR_CALLBACK_FAILURE - * The callback function failed. - */ -int nghttp2_session_close_stream_if_shut_rdwr(nghttp2_session *session, - nghttp2_stream *stream) { - if ((stream->shut_flags & NGHTTP2_SHUT_RDWR) == NGHTTP2_SHUT_RDWR) { - return nghttp2_session_close_stream(session, stream->stream_id, - NGHTTP2_NO_ERROR); - } - return 0; -} - -/* - * Returns nonzero if local endpoint allows reception of new stream - * from remote. - */ -static int session_allow_incoming_new_stream(nghttp2_session *session) { - return (session->goaway_flags & - (NGHTTP2_GOAWAY_TERM_ON_SEND | NGHTTP2_GOAWAY_SENT)) == 0; -} - -/* - * This function returns nonzero if session is closing. - */ -static int session_is_closing(nghttp2_session *session) { - return (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) != 0 || - (nghttp2_session_want_read(session) == 0 && - nghttp2_session_want_write(session) == 0); -} - -/* - * Check that we can send a frame to the |stream|. This function - * returns 0 if we can send a frame to the |frame|, or one of the - * following negative error codes: - * - * NGHTTP2_ERR_STREAM_CLOSED - * The stream is already closed. - * NGHTTP2_ERR_STREAM_SHUT_WR - * The stream is half-closed for transmission. - * NGHTTP2_ERR_SESSION_CLOSING - * This session is closing. - */ -static int session_predicate_for_stream_send(nghttp2_session *session, - nghttp2_stream *stream) { - if (stream == NULL) { - return NGHTTP2_ERR_STREAM_CLOSED; - } - if (session_is_closing(session)) { - return NGHTTP2_ERR_SESSION_CLOSING; - } - if (stream->shut_flags & NGHTTP2_SHUT_WR) { - return NGHTTP2_ERR_STREAM_SHUT_WR; - } - return 0; -} - -int nghttp2_session_check_request_allowed(nghttp2_session *session) { - return !session->server && session->next_stream_id <= INT32_MAX && - (session->goaway_flags & NGHTTP2_GOAWAY_RECV) == 0 && - !session_is_closing(session); -} - -/* - * This function checks request HEADERS frame, which opens stream, can - * be sent at this time. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_START_STREAM_NOT_ALLOWED - * New stream cannot be created because of GOAWAY: session is - * going down or received last_stream_id is strictly less than - * frame->hd.stream_id. - * NGHTTP2_ERR_STREAM_CLOSING - * request HEADERS was canceled by RST_STREAM while it is in queue. - */ -static int session_predicate_request_headers_send(nghttp2_session *session, - nghttp2_outbound_item *item) { - if (item->aux_data.headers.canceled) { - return NGHTTP2_ERR_STREAM_CLOSING; - } - /* If we are terminating session (NGHTTP2_GOAWAY_TERM_ON_SEND), - GOAWAY was received from peer, or session is about to close, new - request is not allowed. */ - if ((session->goaway_flags & NGHTTP2_GOAWAY_RECV) || - session_is_closing(session)) { - return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED; - } - return 0; -} - -/* - * This function checks HEADERS, which is the first frame from the - * server, with the |stream| can be sent at this time. The |stream| - * can be NULL. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_STREAM_CLOSED - * The stream is already closed or does not exist. - * NGHTTP2_ERR_STREAM_SHUT_WR - * The transmission is not allowed for this stream (e.g., a frame - * with END_STREAM flag set has already sent) - * NGHTTP2_ERR_INVALID_STREAM_ID - * The stream ID is invalid. - * NGHTTP2_ERR_STREAM_CLOSING - * RST_STREAM was queued for this stream. - * NGHTTP2_ERR_INVALID_STREAM_STATE - * The state of the stream is not valid. - * NGHTTP2_ERR_SESSION_CLOSING - * This session is closing. - * NGHTTP2_ERR_PROTO - * Client side attempted to send response. - */ -static int session_predicate_response_headers_send(nghttp2_session *session, - nghttp2_stream *stream) { - int rv; - rv = session_predicate_for_stream_send(session, stream); - if (rv != 0) { - return rv; - } - assert(stream); - if (!session->server) { - return NGHTTP2_ERR_PROTO; - } - if (nghttp2_session_is_my_stream_id(session, stream->stream_id)) { - return NGHTTP2_ERR_INVALID_STREAM_ID; - } - switch (stream->state) { - case NGHTTP2_STREAM_OPENING: - return 0; - case NGHTTP2_STREAM_CLOSING: - return NGHTTP2_ERR_STREAM_CLOSING; - default: - return NGHTTP2_ERR_INVALID_STREAM_STATE; - } -} - -/* - * This function checks HEADERS for reserved stream can be sent. The - * |stream| must be reserved state and the |session| is server side. - * The |stream| can be NULL. - * - * This function returns 0 if it succeeds, or one of the following - * error codes: - * - * NGHTTP2_ERR_STREAM_CLOSED - * The stream is already closed. - * NGHTTP2_ERR_STREAM_SHUT_WR - * The stream is half-closed for transmission. - * NGHTTP2_ERR_PROTO - * The stream is not reserved state - * NGHTTP2_ERR_STREAM_CLOSED - * RST_STREAM was queued for this stream. - * NGHTTP2_ERR_SESSION_CLOSING - * This session is closing. - * NGHTTP2_ERR_START_STREAM_NOT_ALLOWED - * New stream cannot be created because GOAWAY is already sent or - * received. - * NGHTTP2_ERR_PROTO - * Client side attempted to send push response. - */ -static int -session_predicate_push_response_headers_send(nghttp2_session *session, - nghttp2_stream *stream) { - int rv; - /* TODO Should disallow HEADERS if GOAWAY has already been issued? */ - rv = session_predicate_for_stream_send(session, stream); - if (rv != 0) { - return rv; - } - assert(stream); - if (!session->server) { - return NGHTTP2_ERR_PROTO; - } - if (stream->state != NGHTTP2_STREAM_RESERVED) { - return NGHTTP2_ERR_PROTO; - } - if (session->goaway_flags & NGHTTP2_GOAWAY_RECV) { - return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED; - } - return 0; -} - -/* - * This function checks HEADERS, which is neither stream-opening nor - * first response header, with the |stream| can be sent at this time. - * The |stream| can be NULL. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_STREAM_CLOSED - * The stream is already closed or does not exist. - * NGHTTP2_ERR_STREAM_SHUT_WR - * The transmission is not allowed for this stream (e.g., a frame - * with END_STREAM flag set has already sent) - * NGHTTP2_ERR_STREAM_CLOSING - * RST_STREAM was queued for this stream. - * NGHTTP2_ERR_INVALID_STREAM_STATE - * The state of the stream is not valid. - * NGHTTP2_ERR_SESSION_CLOSING - * This session is closing. - */ -static int session_predicate_headers_send(nghttp2_session *session, - nghttp2_stream *stream) { - int rv; - rv = session_predicate_for_stream_send(session, stream); - if (rv != 0) { - return rv; - } - assert(stream); - - switch (stream->state) { - case NGHTTP2_STREAM_OPENED: - return 0; - case NGHTTP2_STREAM_CLOSING: - return NGHTTP2_ERR_STREAM_CLOSING; - default: - if (nghttp2_session_is_my_stream_id(session, stream->stream_id)) { - return 0; - } - return NGHTTP2_ERR_INVALID_STREAM_STATE; - } -} - -/* - * This function checks PUSH_PROMISE frame |frame| with the |stream| - * can be sent at this time. The |stream| can be NULL. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_START_STREAM_NOT_ALLOWED - * New stream cannot be created because GOAWAY is already sent or - * received. - * NGHTTP2_ERR_PROTO - * The client side attempts to send PUSH_PROMISE, or the server - * sends PUSH_PROMISE for the stream not initiated by the client. - * NGHTTP2_ERR_STREAM_CLOSED - * The stream is already closed or does not exist. - * NGHTTP2_ERR_STREAM_CLOSING - * RST_STREAM was queued for this stream. - * NGHTTP2_ERR_STREAM_SHUT_WR - * The transmission is not allowed for this stream (e.g., a frame - * with END_STREAM flag set has already sent) - * NGHTTP2_ERR_PUSH_DISABLED - * The remote peer disabled reception of PUSH_PROMISE. - * NGHTTP2_ERR_SESSION_CLOSING - * This session is closing. - */ -static int session_predicate_push_promise_send(nghttp2_session *session, - nghttp2_stream *stream) { - int rv; - - if (!session->server) { - return NGHTTP2_ERR_PROTO; - } - - rv = session_predicate_for_stream_send(session, stream); - if (rv != 0) { - return rv; - } - - assert(stream); - - if (session->remote_settings.enable_push == 0) { - return NGHTTP2_ERR_PUSH_DISABLED; - } - if (stream->state == NGHTTP2_STREAM_CLOSING) { - return NGHTTP2_ERR_STREAM_CLOSING; - } - if (session->goaway_flags & NGHTTP2_GOAWAY_RECV) { - return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED; - } - return 0; -} - -/* - * This function checks WINDOW_UPDATE with the stream ID |stream_id| - * can be sent at this time. Note that END_STREAM flag of the previous - * frame does not affect the transmission of the WINDOW_UPDATE frame. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_STREAM_CLOSED - * The stream is already closed or does not exist. - * NGHTTP2_ERR_STREAM_CLOSING - * RST_STREAM was queued for this stream. - * NGHTTP2_ERR_INVALID_STREAM_STATE - * The state of the stream is not valid. - * NGHTTP2_ERR_SESSION_CLOSING - * This session is closing. - */ -static int session_predicate_window_update_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) { - /* Connection-level window update */ - return 0; - } - stream = nghttp2_session_get_stream(session, stream_id); - if (stream == NULL) { - return NGHTTP2_ERR_STREAM_CLOSED; - } - if (stream->state == NGHTTP2_STREAM_CLOSING) { - return NGHTTP2_ERR_STREAM_CLOSING; - } - if (state_reserved_local(session, stream)) { - return NGHTTP2_ERR_INVALID_STREAM_STATE; - } - 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; - } - if (stream->state == NGHTTP2_STREAM_CLOSING) { - return NGHTTP2_ERR_STREAM_CLOSING; - } - - return 0; -} - -static int session_predicate_origin_send(nghttp2_session *session) { - if (session_is_closing(session)) { - return NGHTTP2_ERR_SESSION_CLOSING; - } - return 0; -} - -/* Take into account settings max frame size and both connection-level - flow control here */ -static ssize_t -nghttp2_session_enforce_flow_control_limits(nghttp2_session *session, - nghttp2_stream *stream, - ssize_t requested_window_size) { - DEBUGF("send: remote windowsize connection=%d, remote maxframsize=%u, " - "stream(id %d)=%d\n", - session->remote_window_size, session->remote_settings.max_frame_size, - stream->stream_id, stream->remote_window_size); - - return nghttp2_min(nghttp2_min(nghttp2_min(requested_window_size, - stream->remote_window_size), - session->remote_window_size), - (int32_t)session->remote_settings.max_frame_size); -} - -/* - * Returns the maximum length of next data read. If the - * connection-level and/or stream-wise flow control are enabled, the - * return value takes into account those current window sizes. The remote - * settings for max frame size is also taken into account. - */ -static size_t nghttp2_session_next_data_read(nghttp2_session *session, - nghttp2_stream *stream) { - ssize_t window_size; - - window_size = nghttp2_session_enforce_flow_control_limits( - session, stream, NGHTTP2_DATA_PAYLOADLEN); - - DEBUGF("send: available window=%zd\n", window_size); - - return window_size > 0 ? (size_t)window_size : 0; -} - -/* - * This function checks DATA with the |stream| can be sent at this - * time. The |stream| can be NULL. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_STREAM_CLOSED - * The stream is already closed or does not exist. - * NGHTTP2_ERR_STREAM_SHUT_WR - * The transmission is not allowed for this stream (e.g., a frame - * with END_STREAM flag set has already sent) - * NGHTTP2_ERR_STREAM_CLOSING - * RST_STREAM was queued for this stream. - * NGHTTP2_ERR_INVALID_STREAM_STATE - * The state of the stream is not valid. - * NGHTTP2_ERR_SESSION_CLOSING - * This session is closing. - */ -static int nghttp2_session_predicate_data_send(nghttp2_session *session, - nghttp2_stream *stream) { - int rv; - rv = session_predicate_for_stream_send(session, stream); - if (rv != 0) { - return rv; - } - assert(stream); - if (nghttp2_session_is_my_stream_id(session, stream->stream_id)) { - /* Request body data */ - /* If stream->state is NGHTTP2_STREAM_CLOSING, RST_STREAM was - queued but not yet sent. In this case, we won't send DATA - frames. */ - if (stream->state == NGHTTP2_STREAM_CLOSING) { - return NGHTTP2_ERR_STREAM_CLOSING; - } - if (stream->state == NGHTTP2_STREAM_RESERVED) { - return NGHTTP2_ERR_INVALID_STREAM_STATE; - } - return 0; - } - /* Response body data */ - if (stream->state == NGHTTP2_STREAM_OPENED) { - return 0; - } - if (stream->state == NGHTTP2_STREAM_CLOSING) { - return NGHTTP2_ERR_STREAM_CLOSING; - } - return NGHTTP2_ERR_INVALID_STREAM_STATE; -} - -static ssize_t session_call_select_padding(nghttp2_session *session, - const nghttp2_frame *frame, - size_t max_payloadlen) { - ssize_t rv; - - if (frame->hd.length >= max_payloadlen) { - return (ssize_t)frame->hd.length; - } - - if (session->callbacks.select_padding_callback) { - size_t max_paddedlen; - - max_paddedlen = - nghttp2_min(frame->hd.length + NGHTTP2_MAX_PADLEN, max_payloadlen); - - rv = session->callbacks.select_padding_callback( - session, frame, max_paddedlen, session->user_data); - if (rv < (ssize_t)frame->hd.length || rv > (ssize_t)max_paddedlen) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - return rv; - } - return (ssize_t)frame->hd.length; -} - -/* Add padding to HEADERS or PUSH_PROMISE. We use - frame->headers.padlen in this function to use the fact that - frame->push_promise has also padlen in the same position. */ -static int session_headers_add_pad(nghttp2_session *session, - nghttp2_frame *frame) { - int rv; - ssize_t padded_payloadlen; - nghttp2_active_outbound_item *aob; - nghttp2_bufs *framebufs; - size_t padlen; - size_t max_payloadlen; - - aob = &session->aob; - framebufs = &aob->framebufs; - - max_payloadlen = nghttp2_min(NGHTTP2_MAX_PAYLOADLEN, - frame->hd.length + NGHTTP2_MAX_PADLEN); - - padded_payloadlen = - session_call_select_padding(session, frame, max_payloadlen); - - if (nghttp2_is_fatal((int)padded_payloadlen)) { - return (int)padded_payloadlen; - } - - padlen = (size_t)padded_payloadlen - frame->hd.length; - - DEBUGF("send: padding selected: payloadlen=%zd, padlen=%zu\n", - padded_payloadlen, padlen); - - rv = nghttp2_frame_add_pad(framebufs, &frame->hd, padlen, 0); - - if (rv != 0) { - return rv; - } - - frame->headers.padlen = padlen; - - return 0; -} - -static size_t session_estimate_headers_payload(nghttp2_session *session, - const nghttp2_nv *nva, - size_t nvlen, - size_t additional) { - return nghttp2_hd_deflate_bound(&session->hd_deflater, nva, nvlen) + - additional; -} - -static int session_pack_extension(nghttp2_session *session, nghttp2_bufs *bufs, - nghttp2_frame *frame) { - ssize_t rv; - nghttp2_buf *buf; - size_t buflen; - size_t framelen; - - assert(session->callbacks.pack_extension_callback); - - buf = &bufs->head->buf; - buflen = nghttp2_min(nghttp2_buf_avail(buf), NGHTTP2_MAX_PAYLOADLEN); - - rv = session->callbacks.pack_extension_callback(session, buf->last, buflen, - frame, session->user_data); - if (rv == NGHTTP2_ERR_CANCEL) { - return (int)rv; - } - - if (rv < 0 || (size_t)rv > buflen) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - - framelen = (size_t)rv; - - frame->hd.length = framelen; - - assert(buf->pos == buf->last); - buf->last += framelen; - buf->pos -= NGHTTP2_FRAME_HDLEN; - - nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); - - return 0; -} - -/* - * This function serializes frame for transmission. - * - * This function returns 0 if it succeeds, or one of negative error - * codes, including both fatal and non-fatal ones. - */ -static int session_prep_frame(nghttp2_session *session, - nghttp2_outbound_item *item) { - int rv; - nghttp2_frame *frame; - nghttp2_mem *mem; - - mem = &session->mem; - frame = &item->frame; - - switch (frame->hd.type) { - case NGHTTP2_DATA: { - size_t next_readmax; - nghttp2_stream *stream; - - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - - if (stream) { - assert(stream->item == item); - } - - rv = nghttp2_session_predicate_data_send(session, stream); - if (rv != 0) { - // If stream was already closed, nghttp2_session_get_stream() - // returns NULL, but item is still attached to the stream. - // Search stream including closed again. - stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); - if (stream) { - int rv2; - - rv2 = nghttp2_stream_detach_item(stream); - - if (nghttp2_is_fatal(rv2)) { - return rv2; - } - } - - return rv; - } - /* Assuming stream is not NULL */ - assert(stream); - next_readmax = nghttp2_session_next_data_read(session, stream); - - if (next_readmax == 0) { - - /* This must be true since we only pop DATA frame item from - queue when session->remote_window_size > 0 */ - assert(session->remote_window_size > 0); - - rv = nghttp2_stream_defer_item(stream, - NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - session->aob.item = NULL; - active_outbound_item_reset(&session->aob, mem); - return NGHTTP2_ERR_DEFERRED; - } - - rv = nghttp2_session_pack_data(session, &session->aob.framebufs, - next_readmax, frame, &item->aux_data.data, - stream); - if (rv == NGHTTP2_ERR_PAUSE) { - return rv; - } - if (rv == NGHTTP2_ERR_DEFERRED) { - rv = nghttp2_stream_defer_item(stream, NGHTTP2_STREAM_FLAG_DEFERRED_USER); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - session->aob.item = NULL; - active_outbound_item_reset(&session->aob, mem); - return NGHTTP2_ERR_DEFERRED; - } - if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { - rv = nghttp2_stream_detach_item(stream); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - rv = nghttp2_session_add_rst_stream(session, frame->hd.stream_id, - NGHTTP2_INTERNAL_ERROR); - if (nghttp2_is_fatal(rv)) { - return rv; - } - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - if (rv != 0) { - int rv2; - - rv2 = nghttp2_stream_detach_item(stream); - - if (nghttp2_is_fatal(rv2)) { - return rv2; - } - - return rv; - } - return 0; - } - case NGHTTP2_HEADERS: { - nghttp2_headers_aux_data *aux_data; - size_t estimated_payloadlen; - - aux_data = &item->aux_data.headers; - - if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) { - /* initial HEADERS, which opens stream */ - nghttp2_stream *stream; - - stream = nghttp2_session_open_stream( - session, frame->hd.stream_id, NGHTTP2_STREAM_FLAG_NONE, - &frame->headers.pri_spec, NGHTTP2_STREAM_INITIAL, - aux_data->stream_user_data); - - if (stream == NULL) { - return NGHTTP2_ERR_NOMEM; - } - - /* We don't call nghttp2_session_adjust_closed_stream() here, - since we don't keep closed stream in client side */ - - rv = session_predicate_request_headers_send(session, item); - if (rv != 0) { - return rv; - } - - if (session_enforce_http_messaging(session)) { - nghttp2_http_record_request_method(stream, frame); - } - } else { - nghttp2_stream *stream; - - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - - if (stream && stream->state == NGHTTP2_STREAM_RESERVED) { - rv = session_predicate_push_response_headers_send(session, stream); - if (rv == 0) { - frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE; - - if (aux_data->stream_user_data) { - stream->stream_user_data = aux_data->stream_user_data; - } - } - } else if (session_predicate_response_headers_send(session, stream) == - 0) { - frame->headers.cat = NGHTTP2_HCAT_RESPONSE; - rv = 0; - } else { - frame->headers.cat = NGHTTP2_HCAT_HEADERS; - - rv = session_predicate_headers_send(session, stream); - } - - if (rv != 0) { - return rv; - } - } - - estimated_payloadlen = session_estimate_headers_payload( - session, frame->headers.nva, frame->headers.nvlen, - NGHTTP2_PRIORITY_SPECLEN); - - if (estimated_payloadlen > session->max_send_header_block_length) { - return NGHTTP2_ERR_FRAME_SIZE_ERROR; - } - - rv = nghttp2_frame_pack_headers(&session->aob.framebufs, &frame->headers, - &session->hd_deflater); - - if (rv != 0) { - return rv; - } - - DEBUGF("send: before padding, HEADERS serialized in %zd bytes\n", - nghttp2_bufs_len(&session->aob.framebufs)); - - rv = session_headers_add_pad(session, frame); - - if (rv != 0) { - return rv; - } - - DEBUGF("send: HEADERS finally serialized in %zd bytes\n", - nghttp2_bufs_len(&session->aob.framebufs)); - - if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) { - assert(session->last_sent_stream_id < frame->hd.stream_id); - session->last_sent_stream_id = frame->hd.stream_id; - } - - return 0; - } - case NGHTTP2_PRIORITY: { - if (session_is_closing(session)) { - return NGHTTP2_ERR_SESSION_CLOSING; - } - /* PRIORITY frame can be sent at any time and to any stream - ID. */ - nghttp2_frame_pack_priority(&session->aob.framebufs, &frame->priority); - - /* Peer can send PRIORITY frame against idle stream to create - "anchor" in dependency tree. Only client can do this in - nghttp2. In nghttp2, only server retains non-active (closed - or idle) streams in memory, so we don't open stream here. */ - return 0; - } - case NGHTTP2_RST_STREAM: - if (session_is_closing(session)) { - return NGHTTP2_ERR_SESSION_CLOSING; - } - nghttp2_frame_pack_rst_stream(&session->aob.framebufs, &frame->rst_stream); - return 0; - case NGHTTP2_SETTINGS: { - if (frame->hd.flags & NGHTTP2_FLAG_ACK) { - assert(session->obq_flood_counter_ > 0); - --session->obq_flood_counter_; - /* When session is about to close, don't send SETTINGS ACK. - We are required to send SETTINGS without ACK though; for - example, we have to send SETTINGS as a part of connection - preface. */ - if (session_is_closing(session)) { - return NGHTTP2_ERR_SESSION_CLOSING; - } - } - - rv = nghttp2_frame_pack_settings(&session->aob.framebufs, &frame->settings); - if (rv != 0) { - return rv; - } - return 0; - } - case NGHTTP2_PUSH_PROMISE: { - nghttp2_stream *stream; - size_t estimated_payloadlen; - - /* stream could be NULL if associated stream was already - closed. */ - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - - /* predicate should fail if stream is NULL. */ - rv = session_predicate_push_promise_send(session, stream); - if (rv != 0) { - return rv; - } - - assert(stream); - - estimated_payloadlen = session_estimate_headers_payload( - session, frame->push_promise.nva, frame->push_promise.nvlen, 0); - - if (estimated_payloadlen > session->max_send_header_block_length) { - return NGHTTP2_ERR_FRAME_SIZE_ERROR; - } - - rv = nghttp2_frame_pack_push_promise( - &session->aob.framebufs, &frame->push_promise, &session->hd_deflater); - if (rv != 0) { - return rv; - } - rv = session_headers_add_pad(session, frame); - if (rv != 0) { - return rv; - } - - assert(session->last_sent_stream_id + 2 <= - frame->push_promise.promised_stream_id); - session->last_sent_stream_id = frame->push_promise.promised_stream_id; - - return 0; - } - case NGHTTP2_PING: - if (frame->hd.flags & NGHTTP2_FLAG_ACK) { - assert(session->obq_flood_counter_ > 0); - --session->obq_flood_counter_; - } - /* PING frame is allowed to be sent unless termination GOAWAY is - sent */ - if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) { - return NGHTTP2_ERR_SESSION_CLOSING; - } - nghttp2_frame_pack_ping(&session->aob.framebufs, &frame->ping); - return 0; - case NGHTTP2_GOAWAY: - rv = nghttp2_frame_pack_goaway(&session->aob.framebufs, &frame->goaway); - if (rv != 0) { - return rv; - } - session->local_last_stream_id = frame->goaway.last_stream_id; - - return 0; - case NGHTTP2_WINDOW_UPDATE: - rv = session_predicate_window_update_send(session, frame->hd.stream_id); - if (rv != 0) { - return rv; - } - nghttp2_frame_pack_window_update(&session->aob.framebufs, - &frame->window_update); - return 0; - case NGHTTP2_CONTINUATION: - /* We never handle CONTINUATION here. */ - assert(0); - return 0; - default: { - nghttp2_ext_aux_data *aux_data; - - /* extension frame */ - - aux_data = &item->aux_data.ext; - - if (aux_data->builtin == 0) { - if (session_is_closing(session)) { - return NGHTTP2_ERR_SESSION_CLOSING; - } - - return session_pack_extension(session, &session->aob.framebufs, frame); - } - - 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); - - return 0; - case NGHTTP2_ORIGIN: - rv = session_predicate_origin_send(session); - if (rv != 0) { - return rv; - } - - rv = nghttp2_frame_pack_origin(&session->aob.framebufs, &frame->ext); - if (rv != 0) { - return rv; - } - - return 0; - default: - /* Unreachable here */ - assert(0); - return 0; - } - } - } -} - -nghttp2_outbound_item * -nghttp2_session_get_next_ob_item(nghttp2_session *session) { - if (nghttp2_outbound_queue_top(&session->ob_urgent)) { - return nghttp2_outbound_queue_top(&session->ob_urgent); - } - - if (nghttp2_outbound_queue_top(&session->ob_reg)) { - return nghttp2_outbound_queue_top(&session->ob_reg); - } - - if (!session_is_outgoing_concurrent_streams_max(session)) { - if (nghttp2_outbound_queue_top(&session->ob_syn)) { - return nghttp2_outbound_queue_top(&session->ob_syn); - } - } - - if (session->remote_window_size > 0) { - return nghttp2_stream_next_outbound_item(&session->root); - } - - return NULL; -} - -nghttp2_outbound_item * -nghttp2_session_pop_next_ob_item(nghttp2_session *session) { - nghttp2_outbound_item *item; - - item = nghttp2_outbound_queue_top(&session->ob_urgent); - if (item) { - nghttp2_outbound_queue_pop(&session->ob_urgent); - item->queued = 0; - return item; - } - - item = nghttp2_outbound_queue_top(&session->ob_reg); - if (item) { - nghttp2_outbound_queue_pop(&session->ob_reg); - item->queued = 0; - return item; - } - - if (!session_is_outgoing_concurrent_streams_max(session)) { - item = nghttp2_outbound_queue_top(&session->ob_syn); - if (item) { - nghttp2_outbound_queue_pop(&session->ob_syn); - item->queued = 0; - return item; - } - } - - if (session->remote_window_size > 0) { - return nghttp2_stream_next_outbound_item(&session->root); - } - - return NULL; -} - -static int session_call_before_frame_send(nghttp2_session *session, - nghttp2_frame *frame) { - int rv; - if (session->callbacks.before_frame_send_callback) { - rv = session->callbacks.before_frame_send_callback(session, frame, - session->user_data); - if (rv == NGHTTP2_ERR_CANCEL) { - return rv; - } - - if (rv != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } - return 0; -} - -static int session_call_on_frame_send(nghttp2_session *session, - nghttp2_frame *frame) { - int rv; - if (session->callbacks.on_frame_send_callback) { - rv = session->callbacks.on_frame_send_callback(session, frame, - session->user_data); - if (rv != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } - return 0; -} - -static int find_stream_on_goaway_func(nghttp2_map_entry *entry, void *ptr) { - nghttp2_close_stream_on_goaway_arg *arg; - nghttp2_stream *stream; - - arg = (nghttp2_close_stream_on_goaway_arg *)ptr; - stream = (nghttp2_stream *)entry; - - if (nghttp2_session_is_my_stream_id(arg->session, stream->stream_id)) { - if (arg->incoming) { - return 0; - } - } else if (!arg->incoming) { - return 0; - } - - if (stream->state != NGHTTP2_STREAM_IDLE && - (stream->flags & NGHTTP2_STREAM_FLAG_CLOSED) == 0 && - stream->stream_id > arg->last_stream_id) { - /* We are collecting streams to close because we cannot call - nghttp2_session_close_stream() inside nghttp2_map_each(). - Reuse closed_next member.. bad choice? */ - assert(stream->closed_next == NULL); - assert(stream->closed_prev == NULL); - - if (arg->head) { - stream->closed_next = arg->head; - arg->head = stream; - } else { - arg->head = stream; - } - } - - return 0; -} - -/* Closes non-idle and non-closed streams whose stream ID > - last_stream_id. If incoming is nonzero, we are going to close - incoming streams. Otherwise, close outgoing streams. */ -static int session_close_stream_on_goaway(nghttp2_session *session, - int32_t last_stream_id, - int incoming) { - int rv; - nghttp2_stream *stream, *next_stream; - nghttp2_close_stream_on_goaway_arg arg = {session, NULL, last_stream_id, - incoming}; - - rv = nghttp2_map_each(&session->streams, find_stream_on_goaway_func, &arg); - assert(rv == 0); - - stream = arg.head; - while (stream) { - next_stream = stream->closed_next; - stream->closed_next = NULL; - rv = nghttp2_session_close_stream(session, stream->stream_id, - NGHTTP2_REFUSED_STREAM); - - /* stream may be deleted here */ - - stream = next_stream; - - if (nghttp2_is_fatal(rv)) { - /* Clean up closed_next member just in case */ - while (stream) { - next_stream = stream->closed_next; - stream->closed_next = NULL; - stream = next_stream; - } - return rv; - } - } - - return 0; -} - -static void reschedule_stream(nghttp2_stream *stream) { - stream->last_writelen = stream->item->frame.hd.length; - - nghttp2_stream_reschedule(stream); -} - -static int session_update_stream_consumed_size(nghttp2_session *session, - nghttp2_stream *stream, - size_t delta_size); - -static int session_update_connection_consumed_size(nghttp2_session *session, - size_t delta_size); - -static int session_update_recv_connection_window_size(nghttp2_session *session, - size_t delta_size); - -static int session_update_recv_stream_window_size(nghttp2_session *session, - nghttp2_stream *stream, - size_t delta_size, - int send_window_update); - -/* - * Called after a frame is sent. This function runs - * on_frame_send_callback and handles stream closure upon END_STREAM - * or RST_STREAM. This function does not reset session->aob. It is a - * responsibility of session_after_frame_sent2. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_NOMEM - * Out of memory. - * NGHTTP2_ERR_CALLBACK_FAILURE - * The callback function failed. - */ -static int session_after_frame_sent1(nghttp2_session *session) { - int rv; - nghttp2_active_outbound_item *aob = &session->aob; - nghttp2_outbound_item *item = aob->item; - nghttp2_bufs *framebufs = &aob->framebufs; - nghttp2_frame *frame; - nghttp2_stream *stream; - - frame = &item->frame; - - if (frame->hd.type == NGHTTP2_DATA) { - nghttp2_data_aux_data *aux_data; - - aux_data = &item->aux_data.data; - - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - /* We update flow control window after a frame was completely - sent. This is possible because we choose payload length not to - exceed the window */ - session->remote_window_size -= (int32_t)frame->hd.length; - if (stream) { - stream->remote_window_size -= (int32_t)frame->hd.length; - } - - if (stream && aux_data->eof) { - rv = nghttp2_stream_detach_item(stream); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - /* Call on_frame_send_callback after - nghttp2_stream_detach_item(), so that application can issue - nghttp2_submit_data() in the callback. */ - if (session->callbacks.on_frame_send_callback) { - rv = session_call_on_frame_send(session, frame); - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - - if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - int stream_closed; - - stream_closed = - (stream->shut_flags & NGHTTP2_SHUT_RDWR) == NGHTTP2_SHUT_RDWR; - - nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); - - rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); - if (nghttp2_is_fatal(rv)) { - return rv; - } - /* stream may be NULL if it was closed */ - if (stream_closed) { - stream = NULL; - } - } - return 0; - } - - if (session->callbacks.on_frame_send_callback) { - rv = session_call_on_frame_send(session, frame); - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - - return 0; - } - - /* non-DATA frame */ - - if (frame->hd.type == NGHTTP2_HEADERS || - frame->hd.type == NGHTTP2_PUSH_PROMISE) { - if (nghttp2_bufs_next_present(framebufs)) { - DEBUGF("send: CONTINUATION exists, just return\n"); - return 0; - } - } - rv = session_call_on_frame_send(session, frame); - if (nghttp2_is_fatal(rv)) { - return rv; - } - switch (frame->hd.type) { - case NGHTTP2_HEADERS: { - nghttp2_headers_aux_data *aux_data; - - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - if (!stream) { - return 0; - } - - switch (frame->headers.cat) { - case NGHTTP2_HCAT_REQUEST: { - stream->state = NGHTTP2_STREAM_OPENING; - if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); - } - rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); - if (nghttp2_is_fatal(rv)) { - return rv; - } - /* We assume aux_data is a pointer to nghttp2_headers_aux_data */ - aux_data = &item->aux_data.headers; - if (aux_data->data_prd.read_callback) { - /* nghttp2_submit_data() makes a copy of aux_data->data_prd */ - rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, - frame->hd.stream_id, &aux_data->data_prd); - if (nghttp2_is_fatal(rv)) { - return rv; - } - /* TODO nghttp2_submit_data() may fail if stream has already - DATA frame item. We might have to handle it here. */ - } - return 0; - } - case NGHTTP2_HCAT_PUSH_RESPONSE: - stream->flags = (uint8_t)(stream->flags & ~NGHTTP2_STREAM_FLAG_PUSH); - ++session->num_outgoing_streams; - /* Fall through */ - case NGHTTP2_HCAT_RESPONSE: - stream->state = NGHTTP2_STREAM_OPENED; - /* Fall through */ - case NGHTTP2_HCAT_HEADERS: - if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); - } - rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); - if (nghttp2_is_fatal(rv)) { - return rv; - } - /* We assume aux_data is a pointer to nghttp2_headers_aux_data */ - aux_data = &item->aux_data.headers; - if (aux_data->data_prd.read_callback) { - rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, - frame->hd.stream_id, &aux_data->data_prd); - if (nghttp2_is_fatal(rv)) { - return rv; - } - /* TODO nghttp2_submit_data() may fail if stream has already - DATA frame item. We might have to handle it here. */ - } - return 0; - default: - /* Unreachable */ - assert(0); - return 0; - } - } - case NGHTTP2_PRIORITY: - if (session->server) { - return 0; - ; - } - - stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); - - if (!stream) { - if (!session_detect_idle_stream(session, frame->hd.stream_id)) { - return 0; - } - - stream = nghttp2_session_open_stream( - session, frame->hd.stream_id, NGHTTP2_FLAG_NONE, - &frame->priority.pri_spec, NGHTTP2_STREAM_IDLE, NULL); - if (!stream) { - return NGHTTP2_ERR_NOMEM; - } - } else { - rv = nghttp2_session_reprioritize_stream(session, stream, - &frame->priority.pri_spec); - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - - rv = nghttp2_session_adjust_idle_stream(session); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - return 0; - case NGHTTP2_RST_STREAM: - rv = nghttp2_session_close_stream(session, frame->hd.stream_id, - frame->rst_stream.error_code); - if (nghttp2_is_fatal(rv)) { - return rv; - } - return 0; - case NGHTTP2_GOAWAY: { - nghttp2_goaway_aux_data *aux_data; - - aux_data = &item->aux_data.goaway; - - if ((aux_data->flags & NGHTTP2_GOAWAY_AUX_SHUTDOWN_NOTICE) == 0) { - - if (aux_data->flags & NGHTTP2_GOAWAY_AUX_TERM_ON_SEND) { - session->goaway_flags |= NGHTTP2_GOAWAY_TERM_SENT; - } - - session->goaway_flags |= NGHTTP2_GOAWAY_SENT; - - rv = session_close_stream_on_goaway(session, frame->goaway.last_stream_id, - 1); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - - return 0; - } - case NGHTTP2_WINDOW_UPDATE: - if (frame->hd.stream_id == 0) { - session->window_update_queued = 0; - if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) { - rv = session_update_connection_consumed_size(session, 0); - } else { - rv = session_update_recv_connection_window_size(session, 0); - } - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - return 0; - } - - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - if (!stream) { - return 0; - } - - stream->window_update_queued = 0; - - /* We don't have to send WINDOW_UPDATE if END_STREAM from peer - is seen. */ - if (stream->shut_flags & NGHTTP2_SHUT_RD) { - return 0; - } - - if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) { - rv = session_update_stream_consumed_size(session, stream, 0); - } else { - rv = session_update_recv_stream_window_size(session, stream, 0, 1); - } - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - return 0; - default: - return 0; - } -} - -/* - * Called after a frame is sent and session_after_frame_sent1. This - * function is responsible to reset session->aob. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_NOMEM - * Out of memory. - * NGHTTP2_ERR_CALLBACK_FAILURE - * The callback function failed. - */ -static int session_after_frame_sent2(nghttp2_session *session) { - int rv; - nghttp2_active_outbound_item *aob = &session->aob; - nghttp2_outbound_item *item = aob->item; - nghttp2_bufs *framebufs = &aob->framebufs; - nghttp2_frame *frame; - nghttp2_mem *mem; - nghttp2_stream *stream; - nghttp2_data_aux_data *aux_data; - - mem = &session->mem; - frame = &item->frame; - - if (frame->hd.type != NGHTTP2_DATA) { - - if (frame->hd.type == NGHTTP2_HEADERS || - frame->hd.type == NGHTTP2_PUSH_PROMISE) { - - if (nghttp2_bufs_next_present(framebufs)) { - framebufs->cur = framebufs->cur->next; - - DEBUGF("send: next CONTINUATION frame, %zu bytes\n", - nghttp2_buf_len(&framebufs->cur->buf)); - - return 0; - } - } - - active_outbound_item_reset(&session->aob, mem); - - return 0; - } - - /* DATA frame */ - - aux_data = &item->aux_data.data; - - /* On EOF, we have already detached data. Please note that - application may issue nghttp2_submit_data() in - on_frame_send_callback (call from session_after_frame_sent1), - which attach data to stream. We don't want to detach it. */ - if (aux_data->eof) { - active_outbound_item_reset(aob, mem); - - return 0; - } - - /* Reset no_copy here because next write may not use this. */ - aux_data->no_copy = 0; - - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - - /* If session is closed or RST_STREAM was queued, we won't send - further data. */ - if (nghttp2_session_predicate_data_send(session, stream) != 0) { - if (stream) { - rv = nghttp2_stream_detach_item(stream); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - - active_outbound_item_reset(aob, mem); - - return 0; - } - - aob->item = NULL; - active_outbound_item_reset(&session->aob, mem); - - return 0; -} - -static int session_call_send_data(nghttp2_session *session, - nghttp2_outbound_item *item, - nghttp2_bufs *framebufs) { - int rv; - nghttp2_buf *buf; - size_t length; - nghttp2_frame *frame; - nghttp2_data_aux_data *aux_data; - - buf = &framebufs->cur->buf; - frame = &item->frame; - length = frame->hd.length - frame->data.padlen; - aux_data = &item->aux_data.data; - - rv = session->callbacks.send_data_callback(session, frame, buf->pos, length, - &aux_data->data_prd.source, - session->user_data); - - switch (rv) { - case 0: - case NGHTTP2_ERR_WOULDBLOCK: - case NGHTTP2_ERR_PAUSE: - case NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE: - return rv; - default: - return NGHTTP2_ERR_CALLBACK_FAILURE; - } -} - -static ssize_t nghttp2_session_mem_send_internal(nghttp2_session *session, - const uint8_t **data_ptr, - int fast_cb) { - int rv; - nghttp2_active_outbound_item *aob; - nghttp2_bufs *framebufs; - nghttp2_mem *mem; - - mem = &session->mem; - aob = &session->aob; - framebufs = &aob->framebufs; - - /* We may have idle streams more than we expect (e.g., - nghttp2_session_change_stream_priority() or - nghttp2_session_create_idle_stream()). Adjust them here. */ - rv = nghttp2_session_adjust_idle_stream(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - for (;;) { - switch (aob->state) { - case NGHTTP2_OB_POP_ITEM: { - nghttp2_outbound_item *item; - - item = nghttp2_session_pop_next_ob_item(session); - if (item == NULL) { - return 0; - } - - rv = session_prep_frame(session, item); - if (rv == NGHTTP2_ERR_PAUSE) { - return 0; - } - if (rv == NGHTTP2_ERR_DEFERRED) { - DEBUGF("send: frame transmission deferred\n"); - break; - } - if (rv < 0) { - int32_t opened_stream_id = 0; - uint32_t error_code = NGHTTP2_INTERNAL_ERROR; - - DEBUGF("send: frame preparation failed with %s\n", - nghttp2_strerror(rv)); - /* TODO If the error comes from compressor, the connection - must be closed. */ - if (item->frame.hd.type != NGHTTP2_DATA && - session->callbacks.on_frame_not_send_callback && is_non_fatal(rv)) { - nghttp2_frame *frame = &item->frame; - /* The library is responsible for the transmission of - WINDOW_UPDATE frame, so we don't call error callback for - it. */ - if (frame->hd.type != NGHTTP2_WINDOW_UPDATE && - session->callbacks.on_frame_not_send_callback( - session, frame, rv, session->user_data) != 0) { - - nghttp2_outbound_item_free(item, mem); - nghttp2_mem_free(mem, item); - - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } - /* We have to close stream opened by failed request HEADERS - or PUSH_PROMISE. */ - switch (item->frame.hd.type) { - case NGHTTP2_HEADERS: - if (item->frame.headers.cat == NGHTTP2_HCAT_REQUEST) { - opened_stream_id = item->frame.hd.stream_id; - if (item->aux_data.headers.canceled) { - error_code = item->aux_data.headers.error_code; - } else { - /* Set error_code to REFUSED_STREAM so that application - can send request again. */ - error_code = NGHTTP2_REFUSED_STREAM; - } - } - break; - case NGHTTP2_PUSH_PROMISE: - opened_stream_id = item->frame.push_promise.promised_stream_id; - break; - } - if (opened_stream_id) { - /* careful not to override rv */ - int rv2; - rv2 = nghttp2_session_close_stream(session, opened_stream_id, - error_code); - - if (nghttp2_is_fatal(rv2)) { - return rv2; - } - } - - nghttp2_outbound_item_free(item, mem); - nghttp2_mem_free(mem, item); - active_outbound_item_reset(aob, mem); - - if (rv == NGHTTP2_ERR_HEADER_COMP) { - /* If header compression error occurred, should terminiate - connection. */ - rv = nghttp2_session_terminate_session(session, - NGHTTP2_INTERNAL_ERROR); - } - if (nghttp2_is_fatal(rv)) { - return rv; - } - break; - } - - aob->item = item; - - nghttp2_bufs_rewind(framebufs); - - if (item->frame.hd.type != NGHTTP2_DATA) { - nghttp2_frame *frame; - - frame = &item->frame; - - DEBUGF("send: next frame: payloadlen=%zu, type=%u, flags=0x%02x, " - "stream_id=%d\n", - frame->hd.length, frame->hd.type, frame->hd.flags, - frame->hd.stream_id); - - rv = session_call_before_frame_send(session, frame); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (rv == NGHTTP2_ERR_CANCEL) { - int32_t opened_stream_id = 0; - uint32_t error_code = NGHTTP2_INTERNAL_ERROR; - - if (session->callbacks.on_frame_not_send_callback) { - if (session->callbacks.on_frame_not_send_callback( - session, frame, rv, session->user_data) != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } - - /* We have to close stream opened by canceled request - HEADERS or PUSH_PROMISE. */ - switch (item->frame.hd.type) { - case NGHTTP2_HEADERS: - if (item->frame.headers.cat == NGHTTP2_HCAT_REQUEST) { - opened_stream_id = item->frame.hd.stream_id; - /* We don't have to check - item->aux_data.headers.canceled since it has already - been checked. */ - /* Set error_code to REFUSED_STREAM so that application - can send request again. */ - error_code = NGHTTP2_REFUSED_STREAM; - } - break; - case NGHTTP2_PUSH_PROMISE: - opened_stream_id = item->frame.push_promise.promised_stream_id; - break; - } - if (opened_stream_id) { - /* careful not to override rv */ - int rv2; - rv2 = nghttp2_session_close_stream(session, opened_stream_id, - error_code); - - if (nghttp2_is_fatal(rv2)) { - return rv2; - } - } - - active_outbound_item_reset(aob, mem); - - break; - } - } else { - DEBUGF("send: next frame: DATA\n"); - - if (item->aux_data.data.no_copy) { - aob->state = NGHTTP2_OB_SEND_NO_COPY; - break; - } - } - - DEBUGF("send: start transmitting frame type=%u, length=%zd\n", - framebufs->cur->buf.pos[3], - framebufs->cur->buf.last - framebufs->cur->buf.pos); - - aob->state = NGHTTP2_OB_SEND_DATA; - - break; - } - case NGHTTP2_OB_SEND_DATA: { - size_t datalen; - nghttp2_buf *buf; - - buf = &framebufs->cur->buf; - - if (buf->pos == buf->last) { - DEBUGF("send: end transmission of a frame\n"); - - /* Frame has completely sent */ - if (fast_cb) { - rv = session_after_frame_sent2(session); - } else { - rv = session_after_frame_sent1(session); - if (rv < 0) { - /* FATAL */ - assert(nghttp2_is_fatal(rv)); - return rv; - } - rv = session_after_frame_sent2(session); - } - if (rv < 0) { - /* FATAL */ - assert(nghttp2_is_fatal(rv)); - return rv; - } - /* We have already adjusted the next state */ - break; - } - - *data_ptr = buf->pos; - datalen = nghttp2_buf_len(buf); - - /* We increment the offset here. If send_callback does not send - everything, we will adjust it. */ - buf->pos += datalen; - - return (ssize_t)datalen; - } - case NGHTTP2_OB_SEND_NO_COPY: { - nghttp2_stream *stream; - nghttp2_frame *frame; - int pause; - - DEBUGF("send: no copy DATA\n"); - - frame = &aob->item->frame; - - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - if (stream == NULL) { - DEBUGF("send: no copy DATA cancelled because stream was closed\n"); - - active_outbound_item_reset(aob, mem); - - break; - } - - rv = session_call_send_data(session, aob->item, framebufs); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { - rv = nghttp2_stream_detach_item(stream); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - rv = nghttp2_session_add_rst_stream(session, frame->hd.stream_id, - NGHTTP2_INTERNAL_ERROR); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - active_outbound_item_reset(aob, mem); - - break; - } - - if (rv == NGHTTP2_ERR_WOULDBLOCK) { - return 0; - } - - pause = (rv == NGHTTP2_ERR_PAUSE); - - rv = session_after_frame_sent1(session); - if (rv < 0) { - assert(nghttp2_is_fatal(rv)); - return rv; - } - rv = session_after_frame_sent2(session); - if (rv < 0) { - assert(nghttp2_is_fatal(rv)); - return rv; - } - - /* We have already adjusted the next state */ - - if (pause) { - return 0; - } - - break; - } - case NGHTTP2_OB_SEND_CLIENT_MAGIC: { - size_t datalen; - nghttp2_buf *buf; - - buf = &framebufs->cur->buf; - - if (buf->pos == buf->last) { - DEBUGF("send: end transmission of client magic\n"); - active_outbound_item_reset(aob, mem); - break; - } - - *data_ptr = buf->pos; - datalen = nghttp2_buf_len(buf); - - buf->pos += datalen; - - return (ssize_t)datalen; - } - } - } -} - -ssize_t nghttp2_session_mem_send(nghttp2_session *session, - const uint8_t **data_ptr) { - int rv; - ssize_t len; - - *data_ptr = NULL; - - len = nghttp2_session_mem_send_internal(session, data_ptr, 1); - if (len <= 0) { - return len; - } - - if (session->aob.item) { - /* We have to call session_after_frame_sent1 here to handle stream - closure upon transmission of frames. Otherwise, END_STREAM may - be reached to client before we call nghttp2_session_mem_send - again and we may get exceeding number of incoming streams. */ - rv = session_after_frame_sent1(session); - if (rv < 0) { - assert(nghttp2_is_fatal(rv)); - return (ssize_t)rv; - } - } - - return len; -} - -int nghttp2_session_send(nghttp2_session *session) { - const uint8_t *data = NULL; - ssize_t datalen; - ssize_t sentlen; - nghttp2_bufs *framebufs; - - framebufs = &session->aob.framebufs; - - for (;;) { - datalen = nghttp2_session_mem_send_internal(session, &data, 0); - if (datalen <= 0) { - return (int)datalen; - } - sentlen = session->callbacks.send_callback(session, data, (size_t)datalen, - 0, session->user_data); - if (sentlen < 0) { - if (sentlen == NGHTTP2_ERR_WOULDBLOCK) { - /* Transmission canceled. Rewind the offset */ - framebufs->cur->buf.pos -= datalen; - - return 0; - } - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - /* Rewind the offset to the amount of unsent bytes */ - framebufs->cur->buf.pos -= datalen - sentlen; - } -} - -static ssize_t session_recv(nghttp2_session *session, uint8_t *buf, - size_t len) { - ssize_t rv; - rv = session->callbacks.recv_callback(session, buf, len, 0, - session->user_data); - if (rv > 0) { - if ((size_t)rv > len) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } else if (rv < 0 && rv != NGHTTP2_ERR_WOULDBLOCK && rv != NGHTTP2_ERR_EOF) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - return rv; -} - -static int session_call_on_begin_frame(nghttp2_session *session, - const nghttp2_frame_hd *hd) { - int rv; - - if (session->callbacks.on_begin_frame_callback) { - - rv = session->callbacks.on_begin_frame_callback(session, hd, - session->user_data); - - if (rv != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } - - return 0; -} - -static int session_call_on_frame_received(nghttp2_session *session, - nghttp2_frame *frame) { - int rv; - if (session->callbacks.on_frame_recv_callback) { - rv = session->callbacks.on_frame_recv_callback(session, frame, - session->user_data); - if (rv != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } - return 0; -} - -static int session_call_on_begin_headers(nghttp2_session *session, - nghttp2_frame *frame) { - int rv; - DEBUGF("recv: call on_begin_headers callback stream_id=%d\n", - frame->hd.stream_id); - if (session->callbacks.on_begin_headers_callback) { - rv = session->callbacks.on_begin_headers_callback(session, frame, - session->user_data); - if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { - return rv; - } - if (rv != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } - return 0; -} - -static int session_call_on_header(nghttp2_session *session, - const nghttp2_frame *frame, - const nghttp2_hd_nv *nv) { - int rv = 0; - if (session->callbacks.on_header_callback2) { - rv = session->callbacks.on_header_callback2( - session, frame, nv->name, nv->value, nv->flags, session->user_data); - } else if (session->callbacks.on_header_callback) { - rv = session->callbacks.on_header_callback( - session, frame, nv->name->base, nv->name->len, nv->value->base, - nv->value->len, nv->flags, session->user_data); - } - - if (rv == NGHTTP2_ERR_PAUSE || rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { - return rv; - } - if (rv != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static int session_call_on_invalid_header(nghttp2_session *session, - const nghttp2_frame *frame, - const nghttp2_hd_nv *nv) { - int rv; - if (session->callbacks.on_invalid_header_callback2) { - rv = session->callbacks.on_invalid_header_callback2( - session, frame, nv->name, nv->value, nv->flags, session->user_data); - } else if (session->callbacks.on_invalid_header_callback) { - rv = session->callbacks.on_invalid_header_callback( - session, frame, nv->name->base, nv->name->len, nv->value->base, - nv->value->len, nv->flags, session->user_data); - } else { - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - - if (rv == NGHTTP2_ERR_PAUSE || rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { - return rv; - } - if (rv != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static int -session_call_on_extension_chunk_recv_callback(nghttp2_session *session, - const uint8_t *data, size_t len) { - int rv; - nghttp2_inbound_frame *iframe = &session->iframe; - nghttp2_frame *frame = &iframe->frame; - - if (session->callbacks.on_extension_chunk_recv_callback) { - rv = session->callbacks.on_extension_chunk_recv_callback( - session, &frame->hd, data, len, session->user_data); - if (rv == NGHTTP2_ERR_CANCEL) { - return rv; - } - if (rv != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } - - return 0; -} - -static int session_call_unpack_extension_callback(nghttp2_session *session) { - int rv; - nghttp2_inbound_frame *iframe = &session->iframe; - nghttp2_frame *frame = &iframe->frame; - void *payload = NULL; - - rv = session->callbacks.unpack_extension_callback( - session, &payload, &frame->hd, session->user_data); - if (rv == NGHTTP2_ERR_CANCEL) { - return rv; - } - if (rv != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - - frame->ext.payload = payload; - - return 0; -} - -/* - * Handles frame size error. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_NOMEM - * Out of memory. - */ -static int session_handle_frame_size_error(nghttp2_session *session) { - /* TODO Currently no callback is called for this error, because we - call this callback before reading any payload */ - return nghttp2_session_terminate_session(session, NGHTTP2_FRAME_SIZE_ERROR); -} - -static uint32_t get_error_code_from_lib_error_code(int lib_error_code) { - switch (lib_error_code) { - case NGHTTP2_ERR_STREAM_CLOSED: - return NGHTTP2_STREAM_CLOSED; - case NGHTTP2_ERR_HEADER_COMP: - return NGHTTP2_COMPRESSION_ERROR; - case NGHTTP2_ERR_FRAME_SIZE_ERROR: - return NGHTTP2_FRAME_SIZE_ERROR; - case NGHTTP2_ERR_FLOW_CONTROL: - return NGHTTP2_FLOW_CONTROL_ERROR; - case NGHTTP2_ERR_REFUSED_STREAM: - return NGHTTP2_REFUSED_STREAM; - case NGHTTP2_ERR_PROTO: - case NGHTTP2_ERR_HTTP_HEADER: - case NGHTTP2_ERR_HTTP_MESSAGING: - return NGHTTP2_PROTOCOL_ERROR; - default: - return NGHTTP2_INTERNAL_ERROR; - } -} - -/* - * Calls on_invalid_frame_recv_callback if it is set to |session|. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_CALLBACK_FAILURE - * User defined callback function fails. - */ -static int session_call_on_invalid_frame_recv_callback(nghttp2_session *session, - nghttp2_frame *frame, - int lib_error_code) { - if (session->callbacks.on_invalid_frame_recv_callback) { - if (session->callbacks.on_invalid_frame_recv_callback( - session, frame, lib_error_code, session->user_data) != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } - return 0; -} - -static int session_handle_invalid_stream2(nghttp2_session *session, - int32_t stream_id, - nghttp2_frame *frame, - int lib_error_code) { - int rv; - rv = nghttp2_session_add_rst_stream( - session, stream_id, get_error_code_from_lib_error_code(lib_error_code)); - if (rv != 0) { - return rv; - } - if (session->callbacks.on_invalid_frame_recv_callback) { - if (session->callbacks.on_invalid_frame_recv_callback( - session, frame, lib_error_code, session->user_data) != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } - return 0; -} - -static int session_handle_invalid_stream(nghttp2_session *session, - nghttp2_frame *frame, - int lib_error_code) { - return session_handle_invalid_stream2(session, frame->hd.stream_id, frame, - lib_error_code); -} - -static int session_inflate_handle_invalid_stream(nghttp2_session *session, - nghttp2_frame *frame, - int lib_error_code) { - int rv; - rv = session_handle_invalid_stream(session, frame, lib_error_code); - if (nghttp2_is_fatal(rv)) { - return rv; - } - return NGHTTP2_ERR_IGN_HEADER_BLOCK; -} - -/* - * Handles invalid frame which causes connection error. - */ -static int session_handle_invalid_connection(nghttp2_session *session, - nghttp2_frame *frame, - int lib_error_code, - const char *reason) { - if (session->callbacks.on_invalid_frame_recv_callback) { - if (session->callbacks.on_invalid_frame_recv_callback( - session, frame, lib_error_code, session->user_data) != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } - return nghttp2_session_terminate_session_with_reason( - session, get_error_code_from_lib_error_code(lib_error_code), reason); -} - -static int session_inflate_handle_invalid_connection(nghttp2_session *session, - nghttp2_frame *frame, - int lib_error_code, - const char *reason) { - int rv; - rv = - session_handle_invalid_connection(session, frame, lib_error_code, reason); - if (nghttp2_is_fatal(rv)) { - return rv; - } - return NGHTTP2_ERR_IGN_HEADER_BLOCK; -} - -/* - * Inflates header block in the memory pointed by |in| with |inlen| - * bytes. If this function returns NGHTTP2_ERR_PAUSE, the caller must - * call this function again, until it returns 0 or one of negative - * error code. If |call_header_cb| is zero, the on_header_callback - * are not invoked and the function never return NGHTTP2_ERR_PAUSE. If - * the given |in| is the last chunk of header block, the |final| must - * be nonzero. If header block is successfully processed (which is - * indicated by the return value 0, NGHTTP2_ERR_PAUSE or - * NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE), the number of processed - * input bytes is assigned to the |*readlen_ptr|. - * - * This function return 0 if it succeeds, or one of the negative error - * codes: - * - * NGHTTP2_ERR_CALLBACK_FAILURE - * The callback function failed. - * NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE - * The callback returns this error code, indicating that this - * stream should be RST_STREAMed. - * NGHTTP2_ERR_NOMEM - * Out of memory. - * NGHTTP2_ERR_PAUSE - * The callback function returned NGHTTP2_ERR_PAUSE - * NGHTTP2_ERR_HEADER_COMP - * Header decompression failed - */ -static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame, - size_t *readlen_ptr, uint8_t *in, size_t inlen, - int final, int call_header_cb) { - ssize_t proclen; - int rv; - int inflate_flags; - nghttp2_hd_nv nv; - nghttp2_stream *stream; - nghttp2_stream *subject_stream; - int trailer = 0; - - *readlen_ptr = 0; - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - - if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { - subject_stream = nghttp2_session_get_stream( - session, frame->push_promise.promised_stream_id); - } else { - subject_stream = stream; - trailer = session_trailer_headers(session, stream, frame); - } - - DEBUGF("recv: decoding header block %zu bytes\n", inlen); - for (;;) { - inflate_flags = 0; - proclen = nghttp2_hd_inflate_hd_nv(&session->hd_inflater, &nv, - &inflate_flags, in, inlen, final); - if (nghttp2_is_fatal((int)proclen)) { - return (int)proclen; - } - if (proclen < 0) { - if (session->iframe.state == NGHTTP2_IB_READ_HEADER_BLOCK) { - if (subject_stream && subject_stream->state != NGHTTP2_STREAM_CLOSING) { - /* Adding RST_STREAM here is very important. It prevents - from invoking subsequent callbacks for the same stream - ID. */ - rv = nghttp2_session_add_rst_stream( - session, subject_stream->stream_id, NGHTTP2_COMPRESSION_ERROR); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - } - rv = - nghttp2_session_terminate_session(session, NGHTTP2_COMPRESSION_ERROR); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - return NGHTTP2_ERR_HEADER_COMP; - } - in += proclen; - inlen -= (size_t)proclen; - *readlen_ptr += (size_t)proclen; - - DEBUGF("recv: proclen=%zd\n", proclen); - - if (call_header_cb && (inflate_flags & NGHTTP2_HD_INFLATE_EMIT)) { - rv = 0; - if (subject_stream && session_enforce_http_messaging(session)) { - rv = nghttp2_http_on_header(session, subject_stream, frame, &nv, - trailer); - - if (rv == NGHTTP2_ERR_IGN_HTTP_HEADER) { - /* Don't overwrite rv here */ - int rv2; - - rv2 = session_call_on_invalid_header(session, frame, &nv); - if (rv2 == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { - rv = NGHTTP2_ERR_HTTP_HEADER; - } else { - if (rv2 != 0) { - return rv2; - } - - /* header is ignored */ - DEBUGF("recv: HTTP ignored: type=%u, id=%d, header %.*s: %.*s\n", - frame->hd.type, frame->hd.stream_id, (int)nv.name->len, - nv.name->base, (int)nv.value->len, nv.value->base); - - rv2 = session_call_error_callback( - session, NGHTTP2_ERR_HTTP_HEADER, - "Ignoring received invalid HTTP header field: frame type: " - "%u, stream: %d, name: [%.*s], value: [%.*s]", - frame->hd.type, frame->hd.stream_id, (int)nv.name->len, - nv.name->base, (int)nv.value->len, nv.value->base); - - if (nghttp2_is_fatal(rv2)) { - return rv2; - } - } - } - - if (rv == NGHTTP2_ERR_HTTP_HEADER) { - DEBUGF("recv: HTTP error: type=%u, id=%d, header %.*s: %.*s\n", - frame->hd.type, frame->hd.stream_id, (int)nv.name->len, - nv.name->base, (int)nv.value->len, nv.value->base); - - rv = session_call_error_callback( - session, NGHTTP2_ERR_HTTP_HEADER, - "Invalid HTTP header field was received: frame type: " - "%u, stream: %d, name: [%.*s], value: [%.*s]", - frame->hd.type, frame->hd.stream_id, (int)nv.name->len, - nv.name->base, (int)nv.value->len, nv.value->base); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - rv = - session_handle_invalid_stream2(session, subject_stream->stream_id, - frame, NGHTTP2_ERR_HTTP_HEADER); - if (nghttp2_is_fatal(rv)) { - return rv; - } - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - } - if (rv == 0) { - rv = session_call_on_header(session, frame, &nv); - /* This handles NGHTTP2_ERR_PAUSE and - NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE as well */ - if (rv != 0) { - return rv; - } - } - } - if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { - nghttp2_hd_inflate_end_headers(&session->hd_inflater); - break; - } - if ((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && inlen == 0) { - break; - } - } - return 0; -} - -/* - * Call this function when HEADERS frame was completely received. - * - * This function returns 0 if it succeeds, or one of negative error - * codes: - * - * NGHTTP2_ERR_CALLBACK_FAILURE - * The callback function failed. - * NGHTTP2_ERR_NOMEM - * Out of memory. - */ -static int session_end_stream_headers_received(nghttp2_session *session, - nghttp2_frame *frame, - nghttp2_stream *stream) { - int rv; - if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { - return 0; - } - - nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); - rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - return 0; -} - -static int session_after_header_block_received(nghttp2_session *session) { - int rv = 0; - int call_cb = 1; - nghttp2_frame *frame = &session->iframe.frame; - nghttp2_stream *stream; - - /* We don't call on_frame_recv_callback if stream has been closed - already or being closed. */ - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - if (!stream || stream->state == NGHTTP2_STREAM_CLOSING) { - return 0; - } - - if (session_enforce_http_messaging(session)) { - if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { - nghttp2_stream *subject_stream; - - subject_stream = nghttp2_session_get_stream( - session, frame->push_promise.promised_stream_id); - if (subject_stream) { - rv = nghttp2_http_on_request_headers(subject_stream, frame); - } - } else { - assert(frame->hd.type == NGHTTP2_HEADERS); - switch (frame->headers.cat) { - case NGHTTP2_HCAT_REQUEST: - rv = nghttp2_http_on_request_headers(stream, frame); - break; - case NGHTTP2_HCAT_RESPONSE: - case NGHTTP2_HCAT_PUSH_RESPONSE: - rv = nghttp2_http_on_response_headers(stream); - break; - case NGHTTP2_HCAT_HEADERS: - if (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) { - assert(!session->server); - rv = nghttp2_http_on_response_headers(stream); - } else { - rv = nghttp2_http_on_trailer_headers(stream, frame); - } - break; - default: - assert(0); - } - if (rv == 0 && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { - rv = nghttp2_http_on_remote_end_stream(stream); - } - } - if (rv != 0) { - int32_t stream_id; - - if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { - stream_id = frame->push_promise.promised_stream_id; - } else { - stream_id = frame->hd.stream_id; - } - - call_cb = 0; - - rv = session_handle_invalid_stream2(session, stream_id, frame, - NGHTTP2_ERR_HTTP_MESSAGING); - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - } - - if (call_cb) { - rv = session_call_on_frame_received(session, frame); - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - - if (frame->hd.type != NGHTTP2_HEADERS) { - return 0; - } - - return session_end_stream_headers_received(session, frame, stream); -} - -int nghttp2_session_on_request_headers_received(nghttp2_session *session, - nghttp2_frame *frame) { - int rv = 0; - nghttp2_stream *stream; - if (frame->hd.stream_id == 0) { - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, "request HEADERS: stream_id == 0"); - } - - /* If client receives idle stream from server, it is invalid - regardless stream ID is even or odd. This is because client is - not expected to receive request from server. */ - if (!session->server) { - if (session_detect_idle_stream(session, frame->hd.stream_id)) { - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, - "request HEADERS: client received request"); - } - - return NGHTTP2_ERR_IGN_HEADER_BLOCK; - } - - assert(session->server); - - if (!session_is_new_peer_stream_id(session, frame->hd.stream_id)) { - if (frame->hd.stream_id == 0 || - nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, - "request HEADERS: invalid stream_id"); - } - - /* RFC 7540 says if an endpoint receives a HEADERS with invalid - * stream ID (e.g, numerically smaller than previous), it MUST - * issue connection error with error code PROTOCOL_ERROR. It is a - * bit hard to detect this, since we cannot remember all streams - * we observed so far. - * - * You might imagine this is really easy. But no. HTTP/2 is - * asynchronous protocol, and usually client and server do not - * share the complete picture of open/closed stream status. For - * example, after server sends RST_STREAM for a stream, client may - * send trailer HEADERS for that stream. If naive server detects - * that, and issued connection error, then it is a bug of server - * implementation since client is not wrong if it did not get - * RST_STREAM when it issued trailer HEADERS. - * - * At the moment, we are very conservative here. We only use - * connection error if stream ID refers idle stream, or we are - * sure that stream is half-closed(remote) or closed. Otherwise - * we just ignore HEADERS for now. - */ - stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); - if (stream && (stream->shut_flags & NGHTTP2_SHUT_RD)) { - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed"); - } - - return NGHTTP2_ERR_IGN_HEADER_BLOCK; - } - session->last_recv_stream_id = frame->hd.stream_id; - - if (session_is_incoming_concurrent_streams_max(session)) { - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, - "request HEADERS: max concurrent streams exceeded"); - } - - if (!session_allow_incoming_new_stream(session)) { - /* We just ignore stream after GOAWAY was sent */ - return NGHTTP2_ERR_IGN_HEADER_BLOCK; - } - - if (frame->headers.pri_spec.stream_id == frame->hd.stream_id) { - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, "request HEADERS: depend on itself"); - } - - if (session_is_incoming_concurrent_streams_pending_max(session)) { - return session_inflate_handle_invalid_stream(session, frame, - NGHTTP2_ERR_REFUSED_STREAM); - } - - stream = nghttp2_session_open_stream( - session, frame->hd.stream_id, NGHTTP2_STREAM_FLAG_NONE, - &frame->headers.pri_spec, NGHTTP2_STREAM_OPENING, NULL); - if (!stream) { - return NGHTTP2_ERR_NOMEM; - } - - rv = nghttp2_session_adjust_closed_stream(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - session->last_proc_stream_id = session->last_recv_stream_id; - - rv = session_call_on_begin_headers(session, frame); - if (rv != 0) { - return rv; - } - return 0; -} - -int nghttp2_session_on_response_headers_received(nghttp2_session *session, - nghttp2_frame *frame, - nghttp2_stream *stream) { - int rv; - /* This function is only called if stream->state == - NGHTTP2_STREAM_OPENING and stream_id is local side initiated. */ - assert(stream->state == NGHTTP2_STREAM_OPENING && - nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)); - if (frame->hd.stream_id == 0) { - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, "response HEADERS: stream_id == 0"); - } - if (stream->shut_flags & NGHTTP2_SHUT_RD) { - /* half closed (remote): from the spec: - - If an endpoint receives additional frames for a stream that is - in this state it MUST respond with a stream error (Section - 5.4.2) of type STREAM_CLOSED. - - We go further, and make it connection error. - */ - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed"); - } - stream->state = NGHTTP2_STREAM_OPENED; - rv = session_call_on_begin_headers(session, frame); - if (rv != 0) { - return rv; - } - return 0; -} - -int nghttp2_session_on_push_response_headers_received(nghttp2_session *session, - nghttp2_frame *frame, - nghttp2_stream *stream) { - int rv = 0; - assert(stream->state == NGHTTP2_STREAM_RESERVED); - if (frame->hd.stream_id == 0) { - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, - "push response HEADERS: stream_id == 0"); - } - - if (session->server) { - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, - "HEADERS: no HEADERS allowed from client in reserved state"); - } - - if (session_is_incoming_concurrent_streams_max(session)) { - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, - "push response HEADERS: max concurrent streams exceeded"); - } - - if (!session_allow_incoming_new_stream(session)) { - /* We don't accept new stream after GOAWAY was sent. */ - return NGHTTP2_ERR_IGN_HEADER_BLOCK; - } - - if (session_is_incoming_concurrent_streams_pending_max(session)) { - return session_inflate_handle_invalid_stream(session, frame, - NGHTTP2_ERR_REFUSED_STREAM); - } - - nghttp2_stream_promise_fulfilled(stream); - if (!nghttp2_session_is_my_stream_id(session, stream->stream_id)) { - --session->num_incoming_reserved_streams; - } - ++session->num_incoming_streams; - rv = session_call_on_begin_headers(session, frame); - if (rv != 0) { - return rv; - } - return 0; -} - -int nghttp2_session_on_headers_received(nghttp2_session *session, - nghttp2_frame *frame, - nghttp2_stream *stream) { - int rv = 0; - if (frame->hd.stream_id == 0) { - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, "HEADERS: stream_id == 0"); - } - if ((stream->shut_flags & NGHTTP2_SHUT_RD)) { - /* half closed (remote): from the spec: - - If an endpoint receives additional frames for a stream that is - in this state it MUST respond with a stream error (Section - 5.4.2) of type STREAM_CLOSED. - - we go further, and make it connection error. - */ - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed"); - } - if (nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { - if (stream->state == NGHTTP2_STREAM_OPENED) { - rv = session_call_on_begin_headers(session, frame); - if (rv != 0) { - return rv; - } - return 0; - } - - return NGHTTP2_ERR_IGN_HEADER_BLOCK; - } - /* If this is remote peer initiated stream, it is OK unless it - has sent END_STREAM frame already. But if stream is in - NGHTTP2_STREAM_CLOSING, we discard the frame. This is a race - condition. */ - if (stream->state != NGHTTP2_STREAM_CLOSING) { - rv = session_call_on_begin_headers(session, frame); - if (rv != 0) { - return rv; - } - return 0; - } - return NGHTTP2_ERR_IGN_HEADER_BLOCK; -} - -static int session_process_headers_frame(nghttp2_session *session) { - int rv; - nghttp2_inbound_frame *iframe = &session->iframe; - nghttp2_frame *frame = &iframe->frame; - nghttp2_stream *stream; - - rv = nghttp2_frame_unpack_headers_payload(&frame->headers, iframe->sbuf.pos); - - if (rv != 0) { - return nghttp2_session_terminate_session_with_reason( - session, NGHTTP2_PROTOCOL_ERROR, "HEADERS: could not unpack"); - } - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - if (!stream) { - frame->headers.cat = NGHTTP2_HCAT_REQUEST; - return nghttp2_session_on_request_headers_received(session, frame); - } - - if (stream->state == NGHTTP2_STREAM_RESERVED) { - frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE; - return nghttp2_session_on_push_response_headers_received(session, frame, - stream); - } - - if (stream->state == NGHTTP2_STREAM_OPENING && - nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { - frame->headers.cat = NGHTTP2_HCAT_RESPONSE; - return nghttp2_session_on_response_headers_received(session, frame, stream); - } - - frame->headers.cat = NGHTTP2_HCAT_HEADERS; - return nghttp2_session_on_headers_received(session, frame, stream); -} - -int nghttp2_session_on_priority_received(nghttp2_session *session, - nghttp2_frame *frame) { - int rv; - nghttp2_stream *stream; - - if (frame->hd.stream_id == 0) { - return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, - "PRIORITY: stream_id == 0"); - } - - if (frame->priority.pri_spec.stream_id == frame->hd.stream_id) { - return nghttp2_session_terminate_session_with_reason( - session, NGHTTP2_PROTOCOL_ERROR, "depend on itself"); - } - - if (!session->server) { - /* Re-prioritization works only in server */ - return session_call_on_frame_received(session, frame); - } - - stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); - - if (!stream) { - /* PRIORITY against idle stream can create anchor node in - dependency tree. */ - if (!session_detect_idle_stream(session, frame->hd.stream_id)) { - return 0; - } - - stream = nghttp2_session_open_stream( - session, frame->hd.stream_id, NGHTTP2_STREAM_FLAG_NONE, - &frame->priority.pri_spec, NGHTTP2_STREAM_IDLE, NULL); - - if (stream == NULL) { - return NGHTTP2_ERR_NOMEM; - } - - rv = nghttp2_session_adjust_idle_stream(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - } else { - rv = nghttp2_session_reprioritize_stream(session, stream, - &frame->priority.pri_spec); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - rv = nghttp2_session_adjust_idle_stream(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - - return session_call_on_frame_received(session, frame); -} - -static int session_process_priority_frame(nghttp2_session *session) { - nghttp2_inbound_frame *iframe = &session->iframe; - nghttp2_frame *frame = &iframe->frame; - - nghttp2_frame_unpack_priority_payload(&frame->priority, iframe->sbuf.pos); - - return nghttp2_session_on_priority_received(session, frame); -} - -int nghttp2_session_on_rst_stream_received(nghttp2_session *session, - nghttp2_frame *frame) { - int rv; - nghttp2_stream *stream; - if (frame->hd.stream_id == 0) { - return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, - "RST_STREAM: stream_id == 0"); - } - - if (session_detect_idle_stream(session, frame->hd.stream_id)) { - return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, - "RST_STREAM: stream in idle"); - } - - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - if (stream) { - /* We may use stream->shut_flags for strict error checking. */ - nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); - } - - rv = session_call_on_frame_received(session, frame); - if (rv != 0) { - return rv; - } - rv = nghttp2_session_close_stream(session, frame->hd.stream_id, - frame->rst_stream.error_code); - if (nghttp2_is_fatal(rv)) { - return rv; - } - return 0; -} - -static int session_process_rst_stream_frame(nghttp2_session *session) { - nghttp2_inbound_frame *iframe = &session->iframe; - nghttp2_frame *frame = &iframe->frame; - - nghttp2_frame_unpack_rst_stream_payload(&frame->rst_stream, iframe->sbuf.pos); - - return nghttp2_session_on_rst_stream_received(session, frame); -} - -static int update_remote_initial_window_size_func(nghttp2_map_entry *entry, - void *ptr) { - int rv; - nghttp2_update_window_size_arg *arg; - nghttp2_stream *stream; - - arg = (nghttp2_update_window_size_arg *)ptr; - stream = (nghttp2_stream *)entry; - - rv = nghttp2_stream_update_remote_initial_window_size( - stream, arg->new_window_size, arg->old_window_size); - if (rv != 0) { - return nghttp2_session_add_rst_stream(arg->session, stream->stream_id, - NGHTTP2_FLOW_CONTROL_ERROR); - } - - /* If window size gets positive, push deferred DATA frame to - outbound queue. */ - if (stream->remote_window_size > 0 && - nghttp2_stream_check_deferred_by_flow_control(stream)) { - - rv = nghttp2_stream_resume_deferred_item( - stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - return 0; -} - -/* - * Updates the remote initial window size of all active streams. If - * error occurs, all streams may not be updated. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_NOMEM - * Out of memory. - */ -static int -session_update_remote_initial_window_size(nghttp2_session *session, - int32_t new_initial_window_size) { - nghttp2_update_window_size_arg arg; - - arg.session = session; - arg.new_window_size = new_initial_window_size; - arg.old_window_size = (int32_t)session->remote_settings.initial_window_size; - - return nghttp2_map_each(&session->streams, - update_remote_initial_window_size_func, &arg); -} - -static int update_local_initial_window_size_func(nghttp2_map_entry *entry, - void *ptr) { - int rv; - nghttp2_update_window_size_arg *arg; - nghttp2_stream *stream; - arg = (nghttp2_update_window_size_arg *)ptr; - stream = (nghttp2_stream *)entry; - rv = nghttp2_stream_update_local_initial_window_size( - stream, arg->new_window_size, arg->old_window_size); - if (rv != 0) { - return nghttp2_session_add_rst_stream(arg->session, stream->stream_id, - NGHTTP2_FLOW_CONTROL_ERROR); - } - if (!(arg->session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) && - stream->window_update_queued == 0 && - nghttp2_should_send_window_update(stream->local_window_size, - stream->recv_window_size)) { - - rv = nghttp2_session_add_window_update(arg->session, NGHTTP2_FLAG_NONE, - stream->stream_id, - stream->recv_window_size); - if (rv != 0) { - return rv; - } - - stream->recv_window_size = 0; - } - return 0; -} - -/* - * Updates the local initial window size of all active streams. If - * error occurs, all streams may not be updated. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_NOMEM - * Out of memory. - */ -static int -session_update_local_initial_window_size(nghttp2_session *session, - int32_t new_initial_window_size, - int32_t old_initial_window_size) { - nghttp2_update_window_size_arg arg; - arg.session = session; - arg.new_window_size = new_initial_window_size; - arg.old_window_size = old_initial_window_size; - return nghttp2_map_each(&session->streams, - update_local_initial_window_size_func, &arg); -} - -/* - * Apply SETTINGS values |iv| having |niv| elements to the local - * settings. We assumes that all values in |iv| is correct, since we - * validated them in nghttp2_session_add_settings() already. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_HEADER_COMP - * The header table size is out of range - * NGHTTP2_ERR_NOMEM - * Out of memory - */ -int nghttp2_session_update_local_settings(nghttp2_session *session, - nghttp2_settings_entry *iv, - size_t niv) { - int rv; - size_t i; - int32_t new_initial_window_size = -1; - uint32_t header_table_size = 0; - uint32_t min_header_table_size = UINT32_MAX; - uint8_t header_table_size_seen = 0; - /* For NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, use the value last - seen. For NGHTTP2_SETTINGS_HEADER_TABLE_SIZE, use both minimum - value and last seen value. */ - for (i = 0; i < niv; ++i) { - switch (iv[i].settings_id) { - case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: - header_table_size_seen = 1; - header_table_size = iv[i].value; - min_header_table_size = nghttp2_min(min_header_table_size, iv[i].value); - break; - case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: - new_initial_window_size = (int32_t)iv[i].value; - break; - } - } - if (header_table_size_seen) { - if (min_header_table_size < header_table_size) { - rv = nghttp2_hd_inflate_change_table_size(&session->hd_inflater, - min_header_table_size); - if (rv != 0) { - return rv; - } - } - - rv = nghttp2_hd_inflate_change_table_size(&session->hd_inflater, - header_table_size); - if (rv != 0) { - return rv; - } - } - if (new_initial_window_size != -1) { - rv = session_update_local_initial_window_size( - session, new_initial_window_size, - (int32_t)session->local_settings.initial_window_size); - if (rv != 0) { - return rv; - } - } - - for (i = 0; i < niv; ++i) { - switch (iv[i].settings_id) { - case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: - session->local_settings.header_table_size = iv[i].value; - break; - case NGHTTP2_SETTINGS_ENABLE_PUSH: - session->local_settings.enable_push = iv[i].value; - break; - case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: - session->local_settings.max_concurrent_streams = iv[i].value; - break; - case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: - session->local_settings.initial_window_size = iv[i].value; - break; - case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: - session->local_settings.max_frame_size = iv[i].value; - break; - case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: - session->local_settings.max_header_list_size = iv[i].value; - break; - case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: - session->local_settings.enable_connect_protocol = iv[i].value; - break; - } - } - - return 0; -} - -int nghttp2_session_on_settings_received(nghttp2_session *session, - nghttp2_frame *frame, int noack) { - int rv; - size_t i; - nghttp2_mem *mem; - nghttp2_inflight_settings *settings; - - mem = &session->mem; - - if (frame->hd.stream_id != 0) { - return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, - "SETTINGS: stream_id != 0"); - } - if (frame->hd.flags & NGHTTP2_FLAG_ACK) { - if (frame->settings.niv != 0) { - return session_handle_invalid_connection( - session, frame, NGHTTP2_ERR_FRAME_SIZE_ERROR, - "SETTINGS: ACK and payload != 0"); - } - - settings = session->inflight_settings_head; - - if (!settings) { - return session_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, "SETTINGS: unexpected ACK"); - } - - rv = nghttp2_session_update_local_settings(session, settings->iv, - settings->niv); - - session->inflight_settings_head = settings->next; - - inflight_settings_del(settings, mem); - - if (rv != 0) { - if (nghttp2_is_fatal(rv)) { - return rv; - } - return session_handle_invalid_connection(session, frame, rv, NULL); - } - return session_call_on_frame_received(session, frame); - } - - if (!session->remote_settings_received) { - session->remote_settings.max_concurrent_streams = - NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS; - session->remote_settings_received = 1; - } - - for (i = 0; i < frame->settings.niv; ++i) { - nghttp2_settings_entry *entry = &frame->settings.iv[i]; - - switch (entry->settings_id) { - case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: - - rv = nghttp2_hd_deflate_change_table_size(&session->hd_deflater, - entry->value); - if (rv != 0) { - if (nghttp2_is_fatal(rv)) { - return rv; - } else { - return session_handle_invalid_connection( - session, frame, NGHTTP2_ERR_HEADER_COMP, NULL); - } - } - - session->remote_settings.header_table_size = entry->value; - - break; - case NGHTTP2_SETTINGS_ENABLE_PUSH: - - if (entry->value != 0 && entry->value != 1) { - return session_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, - "SETTINGS: invalid SETTINGS_ENBLE_PUSH"); - } - - if (!session->server && entry->value != 0) { - return session_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, - "SETTINGS: server attempted to enable push"); - } - - session->remote_settings.enable_push = entry->value; - - break; - case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: - - session->remote_settings.max_concurrent_streams = entry->value; - - break; - case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: - - /* Update the initial window size of the all active streams */ - /* Check that initial_window_size < (1u << 31) */ - if (entry->value > NGHTTP2_MAX_WINDOW_SIZE) { - return session_handle_invalid_connection( - session, frame, NGHTTP2_ERR_FLOW_CONTROL, - "SETTINGS: too large SETTINGS_INITIAL_WINDOW_SIZE"); - } - - rv = session_update_remote_initial_window_size(session, - (int32_t)entry->value); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (rv != 0) { - return session_handle_invalid_connection( - session, frame, NGHTTP2_ERR_FLOW_CONTROL, NULL); - } - - session->remote_settings.initial_window_size = entry->value; - - break; - case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: - - if (entry->value < NGHTTP2_MAX_FRAME_SIZE_MIN || - entry->value > NGHTTP2_MAX_FRAME_SIZE_MAX) { - return session_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, - "SETTINGS: invalid SETTINGS_MAX_FRAME_SIZE"); - } - - session->remote_settings.max_frame_size = entry->value; - - break; - case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: - - session->remote_settings.max_header_list_size = entry->value; - - break; - case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: - - if (entry->value != 0 && entry->value != 1) { - return session_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, - "SETTINGS: invalid SETTINGS_ENABLE_CONNECT_PROTOCOL"); - } - - if (!session->server && - session->remote_settings.enable_connect_protocol && - entry->value == 0) { - return session_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, - "SETTINGS: server attempted to disable " - "SETTINGS_ENABLE_CONNECT_PROTOCOL"); - } - - session->remote_settings.enable_connect_protocol = entry->value; - - break; - } - } - - if (!noack && !session_is_closing(session)) { - rv = nghttp2_session_add_settings(session, NGHTTP2_FLAG_ACK, NULL, 0); - - if (rv != 0) { - if (nghttp2_is_fatal(rv)) { - return rv; - } - - return session_handle_invalid_connection(session, frame, - NGHTTP2_ERR_INTERNAL, NULL); - } - } - - return session_call_on_frame_received(session, frame); -} - -static int session_process_settings_frame(nghttp2_session *session) { - nghttp2_inbound_frame *iframe = &session->iframe; - nghttp2_frame *frame = &iframe->frame; - size_t i; - nghttp2_settings_entry min_header_size_entry; - - if (iframe->max_niv) { - min_header_size_entry = iframe->iv[iframe->max_niv - 1]; - - if (min_header_size_entry.value < UINT32_MAX) { - /* If we have less value, then we must have - SETTINGS_HEADER_TABLE_SIZE in i < iframe->niv */ - for (i = 0; i < iframe->niv; ++i) { - if (iframe->iv[i].settings_id == NGHTTP2_SETTINGS_HEADER_TABLE_SIZE) { - break; - } - } - - assert(i < iframe->niv); - - if (min_header_size_entry.value != iframe->iv[i].value) { - iframe->iv[iframe->niv++] = iframe->iv[i]; - iframe->iv[i] = min_header_size_entry; - } - } - } - - nghttp2_frame_unpack_settings_payload(&frame->settings, iframe->iv, - iframe->niv); - - iframe->iv = NULL; - iframe->niv = 0; - iframe->max_niv = 0; - - return nghttp2_session_on_settings_received(session, frame, 0 /* ACK */); -} - -int nghttp2_session_on_push_promise_received(nghttp2_session *session, - nghttp2_frame *frame) { - int rv; - nghttp2_stream *stream; - nghttp2_stream *promised_stream; - nghttp2_priority_spec pri_spec; - - if (frame->hd.stream_id == 0) { - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: stream_id == 0"); - } - if (session->server || session->local_settings.enable_push == 0) { - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: push disabled"); - } - - if (!nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: invalid stream_id"); - } - - if (!session_allow_incoming_new_stream(session)) { - /* We just discard PUSH_PROMISE after GOAWAY was sent */ - return NGHTTP2_ERR_IGN_HEADER_BLOCK; - } - - if (!session_is_new_peer_stream_id(session, - frame->push_promise.promised_stream_id)) { - /* The spec says if an endpoint receives a PUSH_PROMISE with - illegal stream ID is subject to a connection error of type - PROTOCOL_ERROR. */ - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, - "PUSH_PROMISE: invalid promised_stream_id"); - } - - if (session_detect_idle_stream(session, frame->hd.stream_id)) { - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: stream in idle"); - } - - session->last_recv_stream_id = frame->push_promise.promised_stream_id; - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - if (!stream || stream->state == NGHTTP2_STREAM_CLOSING || - !session->pending_enable_push || - session->num_incoming_reserved_streams >= - session->max_incoming_reserved_streams) { - /* Currently, client does not retain closed stream, so we don't - check NGHTTP2_SHUT_RD condition here. */ - - rv = nghttp2_session_add_rst_stream( - session, frame->push_promise.promised_stream_id, NGHTTP2_CANCEL); - if (rv != 0) { - return rv; - } - return NGHTTP2_ERR_IGN_HEADER_BLOCK; - } - - if (stream->shut_flags & NGHTTP2_SHUT_RD) { - return session_inflate_handle_invalid_connection( - session, frame, NGHTTP2_ERR_STREAM_CLOSED, - "PUSH_PROMISE: stream closed"); - } - - nghttp2_priority_spec_init(&pri_spec, stream->stream_id, - NGHTTP2_DEFAULT_WEIGHT, 0); - - promised_stream = nghttp2_session_open_stream( - session, frame->push_promise.promised_stream_id, NGHTTP2_STREAM_FLAG_NONE, - &pri_spec, NGHTTP2_STREAM_RESERVED, NULL); - - if (!promised_stream) { - return NGHTTP2_ERR_NOMEM; - } - - /* We don't call nghttp2_session_adjust_closed_stream(), since we - don't keep closed stream in client side */ - - session->last_proc_stream_id = session->last_recv_stream_id; - rv = session_call_on_begin_headers(session, frame); - if (rv != 0) { - return rv; - } - return 0; -} - -static int session_process_push_promise_frame(nghttp2_session *session) { - int rv; - nghttp2_inbound_frame *iframe = &session->iframe; - nghttp2_frame *frame = &iframe->frame; - - rv = nghttp2_frame_unpack_push_promise_payload(&frame->push_promise, - iframe->sbuf.pos); - - if (rv != 0) { - return nghttp2_session_terminate_session_with_reason( - session, NGHTTP2_PROTOCOL_ERROR, "PUSH_PROMISE: could not unpack"); - } - - return nghttp2_session_on_push_promise_received(session, frame); -} - -int nghttp2_session_on_ping_received(nghttp2_session *session, - nghttp2_frame *frame) { - int rv = 0; - if (frame->hd.stream_id != 0) { - return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, - "PING: stream_id != 0"); - } - if ((session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_PING_ACK) == 0 && - (frame->hd.flags & NGHTTP2_FLAG_ACK) == 0 && - !session_is_closing(session)) { - /* Peer sent ping, so ping it back */ - rv = nghttp2_session_add_ping(session, NGHTTP2_FLAG_ACK, - frame->ping.opaque_data); - if (rv != 0) { - return rv; - } - } - return session_call_on_frame_received(session, frame); -} - -static int session_process_ping_frame(nghttp2_session *session) { - nghttp2_inbound_frame *iframe = &session->iframe; - nghttp2_frame *frame = &iframe->frame; - - nghttp2_frame_unpack_ping_payload(&frame->ping, iframe->sbuf.pos); - - return nghttp2_session_on_ping_received(session, frame); -} - -int nghttp2_session_on_goaway_received(nghttp2_session *session, - nghttp2_frame *frame) { - int rv; - - if (frame->hd.stream_id != 0) { - return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, - "GOAWAY: stream_id != 0"); - } - /* Spec says Endpoints MUST NOT increase the value they send in the - last stream identifier. */ - if ((frame->goaway.last_stream_id > 0 && - !nghttp2_session_is_my_stream_id(session, - frame->goaway.last_stream_id)) || - session->remote_last_stream_id < frame->goaway.last_stream_id) { - return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, - "GOAWAY: invalid last_stream_id"); - } - - session->goaway_flags |= NGHTTP2_GOAWAY_RECV; - - session->remote_last_stream_id = frame->goaway.last_stream_id; - - rv = session_call_on_frame_received(session, frame); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - return session_close_stream_on_goaway(session, frame->goaway.last_stream_id, - 0); -} - -static int session_process_goaway_frame(nghttp2_session *session) { - nghttp2_inbound_frame *iframe = &session->iframe; - nghttp2_frame *frame = &iframe->frame; - - nghttp2_frame_unpack_goaway_payload(&frame->goaway, iframe->sbuf.pos, - iframe->lbuf.pos, - nghttp2_buf_len(&iframe->lbuf)); - - nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0); - - return nghttp2_session_on_goaway_received(session, frame); -} - -static int -session_on_connection_window_update_received(nghttp2_session *session, - nghttp2_frame *frame) { - /* Handle connection-level flow control */ - if (frame->window_update.window_size_increment == 0) { - return session_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, - "WINDOW_UPDATE: window_size_increment == 0"); - } - - if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment < - session->remote_window_size) { - return session_handle_invalid_connection(session, frame, - NGHTTP2_ERR_FLOW_CONTROL, NULL); - } - session->remote_window_size += frame->window_update.window_size_increment; - - return session_call_on_frame_received(session, frame); -} - -static int session_on_stream_window_update_received(nghttp2_session *session, - nghttp2_frame *frame) { - int rv; - nghttp2_stream *stream; - - if (session_detect_idle_stream(session, frame->hd.stream_id)) { - return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, - "WINDOW_UPDATE to idle stream"); - } - - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - if (!stream) { - return 0; - } - if (state_reserved_remote(session, stream)) { - return session_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, "WINDOW_UPADATE to reserved stream"); - } - if (frame->window_update.window_size_increment == 0) { - return session_handle_invalid_connection( - session, frame, NGHTTP2_ERR_PROTO, - "WINDOW_UPDATE: window_size_increment == 0"); - } - if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment < - stream->remote_window_size) { - return session_handle_invalid_stream(session, frame, - NGHTTP2_ERR_FLOW_CONTROL); - } - stream->remote_window_size += frame->window_update.window_size_increment; - - if (stream->remote_window_size > 0 && - nghttp2_stream_check_deferred_by_flow_control(stream)) { - - rv = nghttp2_stream_resume_deferred_item( - stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - return session_call_on_frame_received(session, frame); -} - -int nghttp2_session_on_window_update_received(nghttp2_session *session, - nghttp2_frame *frame) { - if (frame->hd.stream_id == 0) { - return session_on_connection_window_update_received(session, frame); - } else { - return session_on_stream_window_update_received(session, frame); - } -} - -static int session_process_window_update_frame(nghttp2_session *session) { - nghttp2_inbound_frame *iframe = &session->iframe; - nghttp2_frame *frame = &iframe->frame; - - nghttp2_frame_unpack_window_update_payload(&frame->window_update, - iframe->sbuf.pos); - - 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 session_call_on_invalid_frame_recv_callback(session, frame, - NGHTTP2_ERR_PROTO); - } - } else { - if (altsvc->origin_len > 0) { - return session_call_on_invalid_frame_recv_callback(session, frame, - NGHTTP2_ERR_PROTO); - } - - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - if (!stream) { - return 0; - } - - if (stream->state == NGHTTP2_STREAM_CLOSING) { - return 0; - } - } - - if (altsvc->field_value_len == 0) { - return session_call_on_invalid_frame_recv_callback(session, frame, - NGHTTP2_ERR_PROTO); - } - - return session_call_on_frame_received(session, frame); -} - -int nghttp2_session_on_origin_received(nghttp2_session *session, - nghttp2_frame *frame) { - 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_origin_frame(nghttp2_session *session) { - nghttp2_inbound_frame *iframe = &session->iframe; - nghttp2_frame *frame = &iframe->frame; - nghttp2_mem *mem = &session->mem; - int rv; - - rv = nghttp2_frame_unpack_origin_payload(&frame->ext, iframe->lbuf.pos, - nghttp2_buf_len(&iframe->lbuf), mem); - if (rv != 0) { - if (nghttp2_is_fatal(rv)) { - return rv; - } - /* Ignore ORIGIN frame which cannot be parsed. */ - return 0; - } - - return nghttp2_session_on_origin_received(session, frame); -} - -static int session_process_extension_frame(nghttp2_session *session) { - int rv; - nghttp2_inbound_frame *iframe = &session->iframe; - nghttp2_frame *frame = &iframe->frame; - - rv = session_call_unpack_extension_callback(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - /* This handles the case where rv == NGHTTP2_ERR_CANCEL as well */ - if (rv != 0) { - return 0; - } - - return session_call_on_frame_received(session, frame); -} - -int nghttp2_session_on_data_received(nghttp2_session *session, - nghttp2_frame *frame) { - int rv = 0; - int call_cb = 1; - nghttp2_stream *stream; - - /* We don't call on_frame_recv_callback if stream has been closed - already or being closed. */ - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - if (!stream || stream->state == NGHTTP2_STREAM_CLOSING) { - /* This should be treated as stream error, but it results in lots - of RST_STREAM. So just ignore frame against nonexistent stream - for now. */ - return 0; - } - - if (session_enforce_http_messaging(session) && - (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { - if (nghttp2_http_on_remote_end_stream(stream) != 0) { - call_cb = 0; - rv = nghttp2_session_add_rst_stream(session, stream->stream_id, - NGHTTP2_PROTOCOL_ERROR); - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - } - - if (call_cb) { - rv = session_call_on_frame_received(session, frame); - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - - if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); - rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - return 0; -} - -/* For errors, this function only returns FATAL error. */ -static int session_process_data_frame(nghttp2_session *session) { - int rv; - nghttp2_frame *public_data_frame = &session->iframe.frame; - rv = nghttp2_session_on_data_received(session, public_data_frame); - if (nghttp2_is_fatal(rv)) { - return rv; - } - return 0; -} - -/* - * Now we have SETTINGS synchronization, flow control error can be - * detected strictly. If DATA frame is received with length > 0 and - * current received window size + delta length is strictly larger than - * local window size, it is subject to FLOW_CONTROL_ERROR, so return - * -1. Note that local_window_size is calculated after SETTINGS ACK is - * received from peer, so peer must honor this limit. If the resulting - * recv_window_size is strictly larger than NGHTTP2_MAX_WINDOW_SIZE, - * return -1 too. - */ -static int adjust_recv_window_size(int32_t *recv_window_size_ptr, size_t delta, - int32_t local_window_size) { - if (*recv_window_size_ptr > local_window_size - (int32_t)delta || - *recv_window_size_ptr > NGHTTP2_MAX_WINDOW_SIZE - (int32_t)delta) { - return -1; - } - *recv_window_size_ptr += (int32_t)delta; - return 0; -} - -/* - * Accumulates received bytes |delta_size| for stream-level flow - * control and decides whether to send WINDOW_UPDATE to that stream. - * If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set, WINDOW_UPDATE will not - * be sent. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_NOMEM - * Out of memory. - */ -static int session_update_recv_stream_window_size(nghttp2_session *session, - nghttp2_stream *stream, - size_t delta_size, - int send_window_update) { - int rv; - rv = adjust_recv_window_size(&stream->recv_window_size, delta_size, - stream->local_window_size); - if (rv != 0) { - return nghttp2_session_add_rst_stream(session, stream->stream_id, - NGHTTP2_FLOW_CONTROL_ERROR); - } - /* We don't have to send WINDOW_UPDATE if the data received is the - last chunk in the incoming stream. */ - /* We have to use local_settings here because it is the constraint - the remote endpoint should honor. */ - if (send_window_update && - !(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) && - stream->window_update_queued == 0 && - nghttp2_should_send_window_update(stream->local_window_size, - stream->recv_window_size)) { - rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, - stream->stream_id, - stream->recv_window_size); - if (rv != 0) { - return rv; - } - - stream->recv_window_size = 0; - } - return 0; -} - -/* - * Accumulates received bytes |delta_size| for connection-level flow - * control and decides whether to send WINDOW_UPDATE to the - * connection. If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set, - * WINDOW_UPDATE will not be sent. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_NOMEM - * Out of memory. - */ -static int session_update_recv_connection_window_size(nghttp2_session *session, - size_t delta_size) { - int rv; - rv = adjust_recv_window_size(&session->recv_window_size, delta_size, - session->local_window_size); - if (rv != 0) { - return nghttp2_session_terminate_session(session, - NGHTTP2_FLOW_CONTROL_ERROR); - } - if (!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) && - session->window_update_queued == 0 && - nghttp2_should_send_window_update(session->local_window_size, - session->recv_window_size)) { - /* Use stream ID 0 to update connection-level flow control - window */ - rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, 0, - session->recv_window_size); - if (rv != 0) { - return rv; - } - - session->recv_window_size = 0; - } - return 0; -} - -static int session_update_consumed_size(nghttp2_session *session, - int32_t *consumed_size_ptr, - int32_t *recv_window_size_ptr, - uint8_t window_update_queued, - int32_t stream_id, size_t delta_size, - int32_t local_window_size) { - int32_t recv_size; - int rv; - - if ((size_t)*consumed_size_ptr > NGHTTP2_MAX_WINDOW_SIZE - delta_size) { - return nghttp2_session_terminate_session(session, - NGHTTP2_FLOW_CONTROL_ERROR); - } - - *consumed_size_ptr += (int32_t)delta_size; - - if (window_update_queued == 0) { - /* recv_window_size may be smaller than consumed_size, because it - may be decreased by negative value with - nghttp2_submit_window_update(). */ - recv_size = nghttp2_min(*consumed_size_ptr, *recv_window_size_ptr); - - if (nghttp2_should_send_window_update(local_window_size, recv_size)) { - rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, - stream_id, recv_size); - - if (rv != 0) { - return rv; - } - - *recv_window_size_ptr -= recv_size; - *consumed_size_ptr -= recv_size; - } - } - - return 0; -} - -static int session_update_stream_consumed_size(nghttp2_session *session, - nghttp2_stream *stream, - size_t delta_size) { - return session_update_consumed_size( - session, &stream->consumed_size, &stream->recv_window_size, - stream->window_update_queued, stream->stream_id, delta_size, - stream->local_window_size); -} - -static int session_update_connection_consumed_size(nghttp2_session *session, - size_t delta_size) { - return session_update_consumed_size( - session, &session->consumed_size, &session->recv_window_size, - session->window_update_queued, 0, delta_size, session->local_window_size); -} - -/* - * Checks that we can receive the DATA frame for stream, which is - * indicated by |session->iframe.frame.hd.stream_id|. If it is a - * connection error situation, GOAWAY frame will be issued by this - * function. - * - * If the DATA frame is allowed, returns 0. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_IGN_PAYLOAD - * The reception of DATA frame is connection error; or should be - * ignored. - * NGHTTP2_ERR_NOMEM - * Out of memory. - */ -static int session_on_data_received_fail_fast(nghttp2_session *session) { - int rv; - nghttp2_stream *stream; - nghttp2_inbound_frame *iframe; - int32_t stream_id; - const char *failure_reason; - uint32_t error_code = NGHTTP2_PROTOCOL_ERROR; - - iframe = &session->iframe; - stream_id = iframe->frame.hd.stream_id; - - if (stream_id == 0) { - /* The spec says that if a DATA frame is received whose stream ID - is 0, the recipient MUST respond with a connection error of - type PROTOCOL_ERROR. */ - failure_reason = "DATA: stream_id == 0"; - goto fail; - } - - if (session_detect_idle_stream(session, stream_id)) { - failure_reason = "DATA: stream in idle"; - error_code = NGHTTP2_PROTOCOL_ERROR; - goto fail; - } - - stream = nghttp2_session_get_stream(session, stream_id); - if (!stream) { - stream = nghttp2_session_get_stream_raw(session, stream_id); - if (stream && (stream->shut_flags & NGHTTP2_SHUT_RD)) { - failure_reason = "DATA: stream closed"; - error_code = NGHTTP2_STREAM_CLOSED; - goto fail; - } - - return NGHTTP2_ERR_IGN_PAYLOAD; - } - if (stream->shut_flags & NGHTTP2_SHUT_RD) { - failure_reason = "DATA: stream in half-closed(remote)"; - error_code = NGHTTP2_STREAM_CLOSED; - goto fail; - } - - if (nghttp2_session_is_my_stream_id(session, stream_id)) { - if (stream->state == NGHTTP2_STREAM_CLOSING) { - return NGHTTP2_ERR_IGN_PAYLOAD; - } - if (stream->state != NGHTTP2_STREAM_OPENED) { - failure_reason = "DATA: stream not opened"; - goto fail; - } - return 0; - } - if (stream->state == NGHTTP2_STREAM_RESERVED) { - failure_reason = "DATA: stream in reserved"; - goto fail; - } - if (stream->state == NGHTTP2_STREAM_CLOSING) { - return NGHTTP2_ERR_IGN_PAYLOAD; - } - return 0; -fail: - rv = nghttp2_session_terminate_session_with_reason(session, error_code, - failure_reason); - if (nghttp2_is_fatal(rv)) { - return rv; - } - return NGHTTP2_ERR_IGN_PAYLOAD; -} - -static size_t inbound_frame_payload_readlen(nghttp2_inbound_frame *iframe, - const uint8_t *in, - const uint8_t *last) { - return nghttp2_min((size_t)(last - in), iframe->payloadleft); -} - -/* - * Resets iframe->sbuf and advance its mark pointer by |left| bytes. - */ -static void inbound_frame_set_mark(nghttp2_inbound_frame *iframe, size_t left) { - nghttp2_buf_reset(&iframe->sbuf); - iframe->sbuf.mark += left; -} - -static size_t inbound_frame_buf_read(nghttp2_inbound_frame *iframe, - const uint8_t *in, const uint8_t *last) { - size_t readlen; - - readlen = - nghttp2_min((size_t)(last - in), nghttp2_buf_mark_avail(&iframe->sbuf)); - - iframe->sbuf.last = nghttp2_cpymem(iframe->sbuf.last, in, readlen); - - return readlen; -} - -/* - * Unpacks SETTINGS entry in iframe->sbuf. - */ -static void inbound_frame_set_settings_entry(nghttp2_inbound_frame *iframe) { - nghttp2_settings_entry iv; - nghttp2_settings_entry *min_header_table_size_entry; - size_t i; - - nghttp2_frame_unpack_settings_entry(&iv, iframe->sbuf.pos); - - switch (iv.settings_id) { - case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: - case NGHTTP2_SETTINGS_ENABLE_PUSH: - case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: - case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: - case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: - case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: - case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: - break; - default: - DEBUGF("recv: unknown settings id=0x%02x\n", iv.settings_id); - - iframe->iv[iframe->niv++] = iv; - - return; - } - - for (i = 0; i < iframe->niv; ++i) { - if (iframe->iv[i].settings_id == iv.settings_id) { - iframe->iv[i] = iv; - break; - } - } - - if (i == iframe->niv) { - iframe->iv[iframe->niv++] = iv; - } - - if (iv.settings_id == NGHTTP2_SETTINGS_HEADER_TABLE_SIZE) { - /* Keep track of minimum value of SETTINGS_HEADER_TABLE_SIZE */ - min_header_table_size_entry = &iframe->iv[iframe->max_niv - 1]; - - if (iv.value < min_header_table_size_entry->value) { - min_header_table_size_entry->value = iv.value; - } - } -} - -/* - * Checks PADDED flags and set iframe->sbuf to read them accordingly. - * If padding is set, this function returns 1. If no padding is set, - * this function returns 0. On error, returns -1. - */ -static int inbound_frame_handle_pad(nghttp2_inbound_frame *iframe, - nghttp2_frame_hd *hd) { - if (hd->flags & NGHTTP2_FLAG_PADDED) { - if (hd->length < 1) { - return -1; - } - inbound_frame_set_mark(iframe, 1); - return 1; - } - DEBUGF("recv: no padding in payload\n"); - return 0; -} - -/* - * Computes number of padding based on flags. This function returns - * the calculated length if it succeeds, or -1. - */ -static ssize_t inbound_frame_compute_pad(nghttp2_inbound_frame *iframe) { - size_t padlen; - - /* 1 for Pad Length field */ - padlen = (size_t)(iframe->sbuf.pos[0] + 1); - - DEBUGF("recv: padlen=%zu\n", padlen); - - /* We cannot use iframe->frame.hd.length because of CONTINUATION */ - if (padlen - 1 > iframe->payloadleft) { - return -1; - } - - iframe->padlen = padlen; - - return (ssize_t)padlen; -} - -/* - * This function returns the effective payload length in the data of - * length |readlen| when the remaning payload is |payloadleft|. The - * |payloadleft| does not include |readlen|. If padding was started - * strictly before this data chunk, this function returns -1. - */ -static ssize_t inbound_frame_effective_readlen(nghttp2_inbound_frame *iframe, - size_t payloadleft, - size_t readlen) { - size_t trail_padlen = - nghttp2_frame_trail_padlen(&iframe->frame, iframe->padlen); - - if (trail_padlen > payloadleft) { - size_t padlen; - padlen = trail_padlen - payloadleft; - if (readlen < padlen) { - return -1; - } - return (ssize_t)(readlen - padlen); - } - return (ssize_t)(readlen); -} - -ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, - size_t inlen) { - const uint8_t *first = in, *last = in + inlen; - nghttp2_inbound_frame *iframe = &session->iframe; - size_t readlen; - ssize_t padlen; - int rv; - int busy = 0; - nghttp2_frame_hd cont_hd; - nghttp2_stream *stream; - size_t pri_fieldlen; - nghttp2_mem *mem; - - DEBUGF("recv: connection recv_window_size=%d, local_window=%d\n", - session->recv_window_size, session->local_window_size); - - mem = &session->mem; - - /* We may have idle streams more than we expect (e.g., - nghttp2_session_change_stream_priority() or - nghttp2_session_create_idle_stream()). Adjust them here. */ - rv = nghttp2_session_adjust_idle_stream(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (!nghttp2_session_want_read(session)) { - return (ssize_t)inlen; - } - - for (;;) { - switch (iframe->state) { - case NGHTTP2_IB_READ_CLIENT_MAGIC: - readlen = nghttp2_min(inlen, iframe->payloadleft); - - if (memcmp(NGHTTP2_CLIENT_MAGIC + NGHTTP2_CLIENT_MAGIC_LEN - - iframe->payloadleft, - in, readlen) != 0) { - return NGHTTP2_ERR_BAD_CLIENT_MAGIC; - } - - iframe->payloadleft -= readlen; - in += readlen; - - if (iframe->payloadleft == 0) { - session_inbound_frame_reset(session); - iframe->state = NGHTTP2_IB_READ_FIRST_SETTINGS; - } - - break; - case NGHTTP2_IB_READ_FIRST_SETTINGS: - DEBUGF("recv: [IB_READ_FIRST_SETTINGS]\n"); - - readlen = inbound_frame_buf_read(iframe, in, last); - in += readlen; - - if (nghttp2_buf_mark_avail(&iframe->sbuf)) { - return in - first; - } - - if (iframe->sbuf.pos[3] != NGHTTP2_SETTINGS || - (iframe->sbuf.pos[4] & NGHTTP2_FLAG_ACK)) { - rv = session_call_error_callback( - session, NGHTTP2_ERR_SETTINGS_EXPECTED, - "Remote peer returned unexpected data while we expected " - "SETTINGS frame. Perhaps, peer does not support HTTP/2 " - "properly."); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - rv = nghttp2_session_terminate_session_with_reason( - session, NGHTTP2_PROTOCOL_ERROR, "SETTINGS expected"); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - return (ssize_t)inlen; - } - - iframe->state = NGHTTP2_IB_READ_HEAD; - - /* Fall through */ - case NGHTTP2_IB_READ_HEAD: { - int on_begin_frame_called = 0; - - DEBUGF("recv: [IB_READ_HEAD]\n"); - - readlen = inbound_frame_buf_read(iframe, in, last); - in += readlen; - - if (nghttp2_buf_mark_avail(&iframe->sbuf)) { - return in - first; - } - - nghttp2_frame_unpack_frame_hd(&iframe->frame.hd, iframe->sbuf.pos); - iframe->payloadleft = iframe->frame.hd.length; - - DEBUGF("recv: payloadlen=%zu, type=%u, flags=0x%02x, stream_id=%d\n", - iframe->frame.hd.length, iframe->frame.hd.type, - iframe->frame.hd.flags, iframe->frame.hd.stream_id); - - if (iframe->frame.hd.length > session->local_settings.max_frame_size) { - DEBUGF("recv: length is too large %zu > %u\n", iframe->frame.hd.length, - session->local_settings.max_frame_size); - - rv = nghttp2_session_terminate_session_with_reason( - session, NGHTTP2_FRAME_SIZE_ERROR, "too large frame size"); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - return (ssize_t)inlen; - } - - switch (iframe->frame.hd.type) { - case NGHTTP2_DATA: { - DEBUGF("recv: DATA\n"); - - iframe->frame.hd.flags &= - (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_PADDED); - /* Check stream is open. If it is not open or closing, - ignore payload. */ - busy = 1; - - rv = session_on_data_received_fail_fast(session); - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - if (rv == NGHTTP2_ERR_IGN_PAYLOAD) { - DEBUGF("recv: DATA not allowed stream_id=%d\n", - iframe->frame.hd.stream_id); - iframe->state = NGHTTP2_IB_IGN_DATA; - break; - } - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd); - if (rv < 0) { - rv = nghttp2_session_terminate_session_with_reason( - session, NGHTTP2_PROTOCOL_ERROR, - "DATA: insufficient padding space"); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - return (ssize_t)inlen; - } - - if (rv == 1) { - iframe->state = NGHTTP2_IB_READ_PAD_DATA; - break; - } - - iframe->state = NGHTTP2_IB_READ_DATA; - break; - } - case NGHTTP2_HEADERS: - - DEBUGF("recv: HEADERS\n"); - - iframe->frame.hd.flags &= - (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS | - NGHTTP2_FLAG_PADDED | NGHTTP2_FLAG_PRIORITY); - - rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd); - if (rv < 0) { - rv = nghttp2_session_terminate_session_with_reason( - session, NGHTTP2_PROTOCOL_ERROR, - "HEADERS: insufficient padding space"); - if (nghttp2_is_fatal(rv)) { - return rv; - } - return (ssize_t)inlen; - } - - if (rv == 1) { - iframe->state = NGHTTP2_IB_READ_NBYTE; - break; - } - - pri_fieldlen = nghttp2_frame_priority_len(iframe->frame.hd.flags); - - if (pri_fieldlen > 0) { - if (iframe->payloadleft < pri_fieldlen) { - busy = 1; - iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; - break; - } - - iframe->state = NGHTTP2_IB_READ_NBYTE; - - inbound_frame_set_mark(iframe, pri_fieldlen); - - break; - } - - /* Call on_begin_frame_callback here because - session_process_headers_frame() may call - on_begin_headers_callback */ - rv = session_call_on_begin_frame(session, &iframe->frame.hd); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - on_begin_frame_called = 1; - - rv = session_process_headers_frame(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - busy = 1; - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - - if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { - rv = nghttp2_session_add_rst_stream( - session, iframe->frame.hd.stream_id, NGHTTP2_INTERNAL_ERROR); - if (nghttp2_is_fatal(rv)) { - return rv; - } - iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; - break; - } - - if (rv == NGHTTP2_ERR_IGN_HEADER_BLOCK) { - iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; - break; - } - - iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK; - - break; - case NGHTTP2_PRIORITY: - DEBUGF("recv: PRIORITY\n"); - - iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; - - if (iframe->payloadleft != NGHTTP2_PRIORITY_SPECLEN) { - busy = 1; - - iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; - - break; - } - - iframe->state = NGHTTP2_IB_READ_NBYTE; - - inbound_frame_set_mark(iframe, NGHTTP2_PRIORITY_SPECLEN); - - break; - case NGHTTP2_RST_STREAM: - case NGHTTP2_WINDOW_UPDATE: -#ifdef DEBUGBUILD - switch (iframe->frame.hd.type) { - case NGHTTP2_RST_STREAM: - DEBUGF("recv: RST_STREAM\n"); - break; - case NGHTTP2_WINDOW_UPDATE: - DEBUGF("recv: WINDOW_UPDATE\n"); - break; - } -#endif /* DEBUGBUILD */ - - iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; - - if (iframe->payloadleft != 4) { - busy = 1; - iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; - break; - } - - iframe->state = NGHTTP2_IB_READ_NBYTE; - - inbound_frame_set_mark(iframe, 4); - - break; - case NGHTTP2_SETTINGS: - DEBUGF("recv: SETTINGS\n"); - - iframe->frame.hd.flags &= NGHTTP2_FLAG_ACK; - - if ((iframe->frame.hd.length % NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH) || - ((iframe->frame.hd.flags & NGHTTP2_FLAG_ACK) && - iframe->payloadleft > 0)) { - busy = 1; - iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; - break; - } - - iframe->state = NGHTTP2_IB_READ_SETTINGS; - - if (iframe->payloadleft) { - nghttp2_settings_entry *min_header_table_size_entry; - - /* We allocate iv with additional one entry, to store the - minimum header table size. */ - iframe->max_niv = - iframe->frame.hd.length / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH + 1; - - iframe->iv = nghttp2_mem_malloc(mem, sizeof(nghttp2_settings_entry) * - iframe->max_niv); - - if (!iframe->iv) { - return NGHTTP2_ERR_NOMEM; - } - - min_header_table_size_entry = &iframe->iv[iframe->max_niv - 1]; - min_header_table_size_entry->settings_id = - NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; - min_header_table_size_entry->value = UINT32_MAX; - - inbound_frame_set_mark(iframe, NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH); - break; - } - - busy = 1; - - inbound_frame_set_mark(iframe, 0); - - break; - case NGHTTP2_PUSH_PROMISE: - DEBUGF("recv: PUSH_PROMISE\n"); - - iframe->frame.hd.flags &= - (NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PADDED); - - rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd); - if (rv < 0) { - rv = nghttp2_session_terminate_session_with_reason( - session, NGHTTP2_PROTOCOL_ERROR, - "PUSH_PROMISE: insufficient padding space"); - if (nghttp2_is_fatal(rv)) { - return rv; - } - return (ssize_t)inlen; - } - - if (rv == 1) { - iframe->state = NGHTTP2_IB_READ_NBYTE; - break; - } - - if (iframe->payloadleft < 4) { - busy = 1; - iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; - break; - } - - iframe->state = NGHTTP2_IB_READ_NBYTE; - - inbound_frame_set_mark(iframe, 4); - - break; - case NGHTTP2_PING: - DEBUGF("recv: PING\n"); - - iframe->frame.hd.flags &= NGHTTP2_FLAG_ACK; - - if (iframe->payloadleft != 8) { - busy = 1; - iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; - break; - } - - iframe->state = NGHTTP2_IB_READ_NBYTE; - inbound_frame_set_mark(iframe, 8); - - break; - case NGHTTP2_GOAWAY: - DEBUGF("recv: GOAWAY\n"); - - iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; - - if (iframe->payloadleft < 8) { - busy = 1; - iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; - break; - } - - iframe->state = NGHTTP2_IB_READ_NBYTE; - inbound_frame_set_mark(iframe, 8); - - break; - case NGHTTP2_CONTINUATION: - DEBUGF("recv: unexpected CONTINUATION\n"); - - /* Receiving CONTINUATION in this state are subject to - connection error of type PROTOCOL_ERROR */ - rv = nghttp2_session_terminate_session_with_reason( - session, NGHTTP2_PROTOCOL_ERROR, "CONTINUATION: unexpected"); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - return (ssize_t)inlen; - default: - DEBUGF("recv: extension frame\n"); - - 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_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("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; - case NGHTTP2_ORIGIN: - if (!(session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ORIGIN)) { - busy = 1; - iframe->state = NGHTTP2_IB_IGN_PAYLOAD; - break; - } - - DEBUGF("recv: ORIGIN\n"); - - iframe->frame.ext.payload = &iframe->ext_frame_payload.origin; - - if (session->server || iframe->frame.hd.stream_id || - (iframe->frame.hd.flags & 0xf0)) { - busy = 1; - iframe->state = NGHTTP2_IB_IGN_PAYLOAD; - break; - } - - iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; - - if (iframe->payloadleft) { - iframe->raw_lbuf = nghttp2_mem_malloc(mem, iframe->payloadleft); - - if (iframe->raw_lbuf == NULL) { - return NGHTTP2_ERR_NOMEM; - } - - nghttp2_buf_wrap_init(&iframe->lbuf, iframe->raw_lbuf, - iframe->payloadleft); - } else { - busy = 1; - } - - iframe->state = NGHTTP2_IB_READ_ORIGIN_PAYLOAD; - - break; - default: - busy = 1; - - iframe->state = NGHTTP2_IB_IGN_PAYLOAD; - - break; - } - } - } - - if (!on_begin_frame_called) { - switch (iframe->state) { - case NGHTTP2_IB_IGN_HEADER_BLOCK: - case NGHTTP2_IB_IGN_PAYLOAD: - case NGHTTP2_IB_FRAME_SIZE_ERROR: - case NGHTTP2_IB_IGN_DATA: - case NGHTTP2_IB_IGN_ALL: - break; - default: - rv = session_call_on_begin_frame(session, &iframe->frame.hd); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - } - - break; - } - case NGHTTP2_IB_READ_NBYTE: - DEBUGF("recv: [IB_READ_NBYTE]\n"); - - readlen = inbound_frame_buf_read(iframe, in, last); - in += readlen; - iframe->payloadleft -= readlen; - - DEBUGF("recv: readlen=%zu, payloadleft=%zu, left=%zd\n", readlen, - iframe->payloadleft, nghttp2_buf_mark_avail(&iframe->sbuf)); - - if (nghttp2_buf_mark_avail(&iframe->sbuf)) { - return in - first; - } - - switch (iframe->frame.hd.type) { - case NGHTTP2_HEADERS: - if (iframe->padlen == 0 && - (iframe->frame.hd.flags & NGHTTP2_FLAG_PADDED)) { - pri_fieldlen = nghttp2_frame_priority_len(iframe->frame.hd.flags); - padlen = inbound_frame_compute_pad(iframe); - if (padlen < 0 || - (size_t)padlen + pri_fieldlen > 1 + iframe->payloadleft) { - rv = nghttp2_session_terminate_session_with_reason( - session, NGHTTP2_PROTOCOL_ERROR, "HEADERS: invalid padding"); - if (nghttp2_is_fatal(rv)) { - return rv; - } - return (ssize_t)inlen; - } - iframe->frame.headers.padlen = (size_t)padlen; - - if (pri_fieldlen > 0) { - if (iframe->payloadleft < pri_fieldlen) { - busy = 1; - iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; - break; - } - iframe->state = NGHTTP2_IB_READ_NBYTE; - inbound_frame_set_mark(iframe, pri_fieldlen); - break; - } else { - /* Truncate buffers used for padding spec */ - inbound_frame_set_mark(iframe, 0); - } - } - - rv = session_process_headers_frame(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - busy = 1; - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - - if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { - rv = nghttp2_session_add_rst_stream( - session, iframe->frame.hd.stream_id, NGHTTP2_INTERNAL_ERROR); - if (nghttp2_is_fatal(rv)) { - return rv; - } - iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; - break; - } - - if (rv == NGHTTP2_ERR_IGN_HEADER_BLOCK) { - iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; - break; - } - - iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK; - - break; - case NGHTTP2_PRIORITY: - rv = session_process_priority_frame(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - - session_inbound_frame_reset(session); - - break; - case NGHTTP2_RST_STREAM: - rv = session_process_rst_stream_frame(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - - session_inbound_frame_reset(session); - - break; - case NGHTTP2_PUSH_PROMISE: - if (iframe->padlen == 0 && - (iframe->frame.hd.flags & NGHTTP2_FLAG_PADDED)) { - padlen = inbound_frame_compute_pad(iframe); - if (padlen < 0 || (size_t)padlen + 4 /* promised stream id */ - > 1 + iframe->payloadleft) { - rv = nghttp2_session_terminate_session_with_reason( - session, NGHTTP2_PROTOCOL_ERROR, - "PUSH_PROMISE: invalid padding"); - if (nghttp2_is_fatal(rv)) { - return rv; - } - return (ssize_t)inlen; - } - - iframe->frame.push_promise.padlen = (size_t)padlen; - - if (iframe->payloadleft < 4) { - busy = 1; - iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; - break; - } - - iframe->state = NGHTTP2_IB_READ_NBYTE; - - inbound_frame_set_mark(iframe, 4); - - break; - } - - rv = session_process_push_promise_frame(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - busy = 1; - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - - if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { - rv = nghttp2_session_add_rst_stream( - session, iframe->frame.push_promise.promised_stream_id, - NGHTTP2_INTERNAL_ERROR); - if (nghttp2_is_fatal(rv)) { - return rv; - } - iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; - break; - } - - if (rv == NGHTTP2_ERR_IGN_HEADER_BLOCK) { - iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; - break; - } - - iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK; - - break; - case NGHTTP2_PING: - rv = session_process_ping_frame(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - - session_inbound_frame_reset(session); - - break; - case NGHTTP2_GOAWAY: { - size_t debuglen; - - /* 8 is Last-stream-ID + Error Code */ - debuglen = iframe->frame.hd.length - 8; - - if (debuglen > 0) { - iframe->raw_lbuf = nghttp2_mem_malloc(mem, debuglen); - - if (iframe->raw_lbuf == NULL) { - return NGHTTP2_ERR_NOMEM; - } - - nghttp2_buf_wrap_init(&iframe->lbuf, iframe->raw_lbuf, debuglen); - } - - busy = 1; - - iframe->state = NGHTTP2_IB_READ_GOAWAY_DEBUG; - - break; - } - case NGHTTP2_WINDOW_UPDATE: - rv = session_process_window_update_frame(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - - session_inbound_frame_reset(session); - - break; - case NGHTTP2_ALTSVC: { - size_t origin_len; - - origin_len = nghttp2_get_uint16(iframe->sbuf.pos); - - DEBUGF("recv: origin_len=%zu\n", origin_len); - - if (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); - - break; - } - break; - case NGHTTP2_IB_READ_HEADER_BLOCK: - case NGHTTP2_IB_IGN_HEADER_BLOCK: { - ssize_t data_readlen; - size_t trail_padlen; - int final; -#ifdef DEBUGBUILD - if (iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK) { - DEBUGF("recv: [IB_READ_HEADER_BLOCK]\n"); - } else { - DEBUGF("recv: [IB_IGN_HEADER_BLOCK]\n"); - } -#endif /* DEBUGBUILD */ - - readlen = inbound_frame_payload_readlen(iframe, in, last); - - DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, - iframe->payloadleft - readlen); - - data_readlen = inbound_frame_effective_readlen( - iframe, iframe->payloadleft - readlen, readlen); - - if (data_readlen == -1) { - /* everything is padding */ - data_readlen = 0; - } - - trail_padlen = nghttp2_frame_trail_padlen(&iframe->frame, iframe->padlen); - - final = (iframe->frame.hd.flags & NGHTTP2_FLAG_END_HEADERS) && - iframe->payloadleft - (size_t)data_readlen == trail_padlen; - - if (data_readlen > 0 || (data_readlen == 0 && final)) { - size_t hd_proclen = 0; - - DEBUGF("recv: block final=%d\n", final); - - rv = - inflate_header_block(session, &iframe->frame, &hd_proclen, - (uint8_t *)in, (size_t)data_readlen, final, - iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - - if (rv == NGHTTP2_ERR_PAUSE) { - in += hd_proclen; - iframe->payloadleft -= hd_proclen; - - return in - first; - } - - if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { - /* The application says no more headers. We decompress the - rest of the header block but not invoke on_header_callback - and on_frame_recv_callback. */ - in += hd_proclen; - iframe->payloadleft -= hd_proclen; - - /* Use promised stream ID for PUSH_PROMISE */ - rv = nghttp2_session_add_rst_stream( - session, - iframe->frame.hd.type == NGHTTP2_PUSH_PROMISE - ? iframe->frame.push_promise.promised_stream_id - : iframe->frame.hd.stream_id, - NGHTTP2_INTERNAL_ERROR); - if (nghttp2_is_fatal(rv)) { - return rv; - } - busy = 1; - iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; - break; - } - - in += readlen; - iframe->payloadleft -= readlen; - - if (rv == NGHTTP2_ERR_HEADER_COMP) { - /* GOAWAY is already issued */ - if (iframe->payloadleft == 0) { - session_inbound_frame_reset(session); - } else { - busy = 1; - iframe->state = NGHTTP2_IB_IGN_PAYLOAD; - } - break; - } - } else { - in += readlen; - iframe->payloadleft -= readlen; - } - - if (iframe->payloadleft) { - break; - } - - if ((iframe->frame.hd.flags & NGHTTP2_FLAG_END_HEADERS) == 0) { - - inbound_frame_set_mark(iframe, NGHTTP2_FRAME_HDLEN); - - iframe->padlen = 0; - - if (iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK) { - iframe->state = NGHTTP2_IB_EXPECT_CONTINUATION; - } else { - iframe->state = NGHTTP2_IB_IGN_CONTINUATION; - } - } else { - if (iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK) { - rv = session_after_header_block_received(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - session_inbound_frame_reset(session); - } - break; - } - case NGHTTP2_IB_IGN_PAYLOAD: - DEBUGF("recv: [IB_IGN_PAYLOAD]\n"); - - readlen = inbound_frame_payload_readlen(iframe, in, last); - iframe->payloadleft -= readlen; - in += readlen; - - DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, - iframe->payloadleft); - - if (iframe->payloadleft) { - break; - } - - switch (iframe->frame.hd.type) { - case NGHTTP2_HEADERS: - case NGHTTP2_PUSH_PROMISE: - case NGHTTP2_CONTINUATION: - /* Mark inflater bad so that we won't perform further decoding */ - session->hd_inflater.ctx.bad = 1; - break; - default: - break; - } - - session_inbound_frame_reset(session); - - break; - case NGHTTP2_IB_FRAME_SIZE_ERROR: - DEBUGF("recv: [IB_FRAME_SIZE_ERROR]\n"); - - rv = session_handle_frame_size_error(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - assert(iframe->state == NGHTTP2_IB_IGN_ALL); - - return (ssize_t)inlen; - case NGHTTP2_IB_READ_SETTINGS: - DEBUGF("recv: [IB_READ_SETTINGS]\n"); - - readlen = inbound_frame_buf_read(iframe, in, last); - iframe->payloadleft -= readlen; - in += readlen; - - DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, - iframe->payloadleft); - - if (nghttp2_buf_mark_avail(&iframe->sbuf)) { - break; - } - - if (readlen > 0) { - inbound_frame_set_settings_entry(iframe); - } - if (iframe->payloadleft) { - inbound_frame_set_mark(iframe, NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH); - break; - } - - rv = session_process_settings_frame(session); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - - session_inbound_frame_reset(session); - - break; - case NGHTTP2_IB_READ_GOAWAY_DEBUG: - DEBUGF("recv: [IB_READ_GOAWAY_DEBUG]\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("recv: readlen=%zu, payloadleft=%zu\n", readlen, - iframe->payloadleft); - - if (iframe->payloadleft) { - assert(nghttp2_buf_avail(&iframe->lbuf) > 0); - - break; - } - - rv = session_process_goaway_frame(session); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - - session_inbound_frame_reset(session); - - break; - case NGHTTP2_IB_EXPECT_CONTINUATION: - case NGHTTP2_IB_IGN_CONTINUATION: -#ifdef DEBUGBUILD - if (iframe->state == NGHTTP2_IB_EXPECT_CONTINUATION) { - fprintf(stderr, "recv: [IB_EXPECT_CONTINUATION]\n"); - } else { - fprintf(stderr, "recv: [IB_IGN_CONTINUATION]\n"); - } -#endif /* DEBUGBUILD */ - - readlen = inbound_frame_buf_read(iframe, in, last); - in += readlen; - - if (nghttp2_buf_mark_avail(&iframe->sbuf)) { - return in - first; - } - - nghttp2_frame_unpack_frame_hd(&cont_hd, iframe->sbuf.pos); - iframe->payloadleft = cont_hd.length; - - DEBUGF("recv: payloadlen=%zu, type=%u, flags=0x%02x, stream_id=%d\n", - cont_hd.length, cont_hd.type, cont_hd.flags, cont_hd.stream_id); - - if (cont_hd.type != NGHTTP2_CONTINUATION || - cont_hd.stream_id != iframe->frame.hd.stream_id) { - DEBUGF("recv: expected stream_id=%d, type=%d, but got stream_id=%d, " - "type=%u\n", - iframe->frame.hd.stream_id, NGHTTP2_CONTINUATION, - cont_hd.stream_id, cont_hd.type); - rv = nghttp2_session_terminate_session_with_reason( - session, NGHTTP2_PROTOCOL_ERROR, - "unexpected non-CONTINUATION frame or stream_id is invalid"); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - return (ssize_t)inlen; - } - - /* CONTINUATION won't bear NGHTTP2_PADDED flag */ - - iframe->frame.hd.flags = (uint8_t)( - iframe->frame.hd.flags | (cont_hd.flags & NGHTTP2_FLAG_END_HEADERS)); - iframe->frame.hd.length += cont_hd.length; - - busy = 1; - - if (iframe->state == NGHTTP2_IB_EXPECT_CONTINUATION) { - iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK; - - rv = session_call_on_begin_frame(session, &cont_hd); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - } else { - iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; - } - - break; - case NGHTTP2_IB_READ_PAD_DATA: - DEBUGF("recv: [IB_READ_PAD_DATA]\n"); - - readlen = inbound_frame_buf_read(iframe, in, last); - in += readlen; - iframe->payloadleft -= readlen; - - DEBUGF("recv: readlen=%zu, payloadleft=%zu, left=%zu\n", readlen, - iframe->payloadleft, nghttp2_buf_mark_avail(&iframe->sbuf)); - - if (nghttp2_buf_mark_avail(&iframe->sbuf)) { - return in - first; - } - - /* Pad Length field is subject to flow control */ - rv = session_update_recv_connection_window_size(session, readlen); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - - /* Pad Length field is consumed immediately */ - rv = - nghttp2_session_consume(session, iframe->frame.hd.stream_id, readlen); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - - stream = nghttp2_session_get_stream(session, iframe->frame.hd.stream_id); - if (stream) { - rv = session_update_recv_stream_window_size( - session, stream, readlen, - iframe->payloadleft || - (iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0); - if (nghttp2_is_fatal(rv)) { - return rv; - } - } - - busy = 1; - - padlen = inbound_frame_compute_pad(iframe); - if (padlen < 0) { - rv = nghttp2_session_terminate_session_with_reason( - session, NGHTTP2_PROTOCOL_ERROR, "DATA: invalid padding"); - if (nghttp2_is_fatal(rv)) { - return rv; - } - return (ssize_t)inlen; - } - - iframe->frame.data.padlen = (size_t)padlen; - - iframe->state = NGHTTP2_IB_READ_DATA; - - break; - case NGHTTP2_IB_READ_DATA: - stream = nghttp2_session_get_stream(session, iframe->frame.hd.stream_id); - - if (!stream) { - busy = 1; - iframe->state = NGHTTP2_IB_IGN_DATA; - break; - } - - DEBUGF("recv: [IB_READ_DATA]\n"); - - readlen = inbound_frame_payload_readlen(iframe, in, last); - iframe->payloadleft -= readlen; - in += readlen; - - DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, - iframe->payloadleft); - - if (readlen > 0) { - ssize_t data_readlen; - - rv = session_update_recv_connection_window_size(session, readlen); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - - rv = session_update_recv_stream_window_size( - session, stream, readlen, - iframe->payloadleft || - (iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - data_readlen = inbound_frame_effective_readlen( - iframe, iframe->payloadleft, readlen); - - if (data_readlen == -1) { - /* everything is padding */ - data_readlen = 0; - } - - padlen = (ssize_t)readlen - data_readlen; - - if (padlen > 0) { - /* Padding is considered as "consumed" immediately */ - rv = nghttp2_session_consume(session, iframe->frame.hd.stream_id, - (size_t)padlen); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - } - - DEBUGF("recv: data_readlen=%zd\n", data_readlen); - - if (data_readlen > 0) { - if (session_enforce_http_messaging(session)) { - if (nghttp2_http_on_data_chunk(stream, (size_t)data_readlen) != 0) { - if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) { - /* Consume all data for connection immediately here */ - rv = session_update_connection_consumed_size( - session, (size_t)data_readlen); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (iframe->state == NGHTTP2_IB_IGN_DATA) { - return (ssize_t)inlen; - } - } - - rv = nghttp2_session_add_rst_stream( - session, iframe->frame.hd.stream_id, NGHTTP2_PROTOCOL_ERROR); - if (nghttp2_is_fatal(rv)) { - return rv; - } - busy = 1; - iframe->state = NGHTTP2_IB_IGN_DATA; - break; - } - } - if (session->callbacks.on_data_chunk_recv_callback) { - rv = session->callbacks.on_data_chunk_recv_callback( - session, iframe->frame.hd.flags, iframe->frame.hd.stream_id, - in - readlen, (size_t)data_readlen, session->user_data); - if (rv == NGHTTP2_ERR_PAUSE) { - return in - first; - } - - if (nghttp2_is_fatal(rv)) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } - } - } - - if (iframe->payloadleft) { - break; - } - - rv = session_process_data_frame(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - session_inbound_frame_reset(session); - - break; - case NGHTTP2_IB_IGN_DATA: - DEBUGF("recv: [IB_IGN_DATA]\n"); - - readlen = inbound_frame_payload_readlen(iframe, in, last); - iframe->payloadleft -= readlen; - in += readlen; - - DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, - iframe->payloadleft); - - if (readlen > 0) { - /* Update connection-level flow control window for ignored - DATA frame too */ - rv = session_update_recv_connection_window_size(session, readlen); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - - if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) { - - /* Ignored DATA is considered as "consumed" immediately. */ - rv = session_update_connection_consumed_size(session, readlen); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - } - } - - if (iframe->payloadleft) { - break; - } - - session_inbound_frame_reset(session); - - break; - case NGHTTP2_IB_IGN_ALL: - return (ssize_t)inlen; - case NGHTTP2_IB_READ_EXTENSION_PAYLOAD: - DEBUGF("recv: [IB_READ_EXTENSION_PAYLOAD]\n"); - - readlen = inbound_frame_payload_readlen(iframe, in, last); - iframe->payloadleft -= readlen; - in += readlen; - - DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, - iframe->payloadleft); - - if (readlen > 0) { - rv = session_call_on_extension_chunk_recv_callback( - session, in - readlen, readlen); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (rv != 0) { - busy = 1; - - iframe->state = NGHTTP2_IB_IGN_PAYLOAD; - - break; - } - } - - if (iframe->payloadleft > 0) { - break; - } - - rv = session_process_extension_frame(session); - if (nghttp2_is_fatal(rv)) { - return rv; - } - - session_inbound_frame_reset(session); - - break; - case NGHTTP2_IB_READ_ALTSVC_PAYLOAD: - DEBUGF("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("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; - case NGHTTP2_IB_READ_ORIGIN_PAYLOAD: - DEBUGF("recv: [IB_READ_ORIGIN_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("recv: readlen=%zu, payloadleft=%zu\n", readlen, - iframe->payloadleft); - - if (iframe->payloadleft) { - assert(nghttp2_buf_avail(&iframe->lbuf) > 0); - - break; - } - - rv = session_process_origin_frame(session); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - if (iframe->state == NGHTTP2_IB_IGN_ALL) { - return (ssize_t)inlen; - } - - session_inbound_frame_reset(session); - - break; - } - - if (!busy && in == last) { - break; - } - - busy = 0; - } - - assert(in == last); - - return in - first; -} - -int nghttp2_session_recv(nghttp2_session *session) { - uint8_t buf[NGHTTP2_INBOUND_BUFFER_LENGTH]; - while (1) { - ssize_t readlen; - readlen = session_recv(session, buf, sizeof(buf)); - if (readlen > 0) { - ssize_t proclen = nghttp2_session_mem_recv(session, buf, (size_t)readlen); - if (proclen < 0) { - return (int)proclen; - } - assert(proclen == readlen); - } else if (readlen == 0 || readlen == NGHTTP2_ERR_WOULDBLOCK) { - return 0; - } else if (readlen == NGHTTP2_ERR_EOF) { - return NGHTTP2_ERR_EOF; - } else if (readlen < 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } -} - -/* - * Returns the number of active streams, which includes streams in - * reserved state. - */ -static size_t session_get_num_active_streams(nghttp2_session *session) { - return nghttp2_map_size(&session->streams) - session->num_closed_streams - - session->num_idle_streams; -} - -int nghttp2_session_want_read(nghttp2_session *session) { - size_t num_active_streams; - - /* If this flag is set, we don't want to read. The application - should drop the connection. */ - if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_SENT) { - return 0; - } - - num_active_streams = session_get_num_active_streams(session); - - /* Unless termination GOAWAY is sent or received, we always want to - read incoming frames. */ - - if (num_active_streams > 0) { - return 1; - } - - /* If there is no active streams and GOAWAY has been sent or - received, we are done with this session. */ - return (session->goaway_flags & - (NGHTTP2_GOAWAY_SENT | NGHTTP2_GOAWAY_RECV)) == 0; -} - -int nghttp2_session_want_write(nghttp2_session *session) { - /* If these flag is set, we don't want to write any data. The - application should drop the connection. */ - if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_SENT) { - return 0; - } - - /* - * Unless termination GOAWAY is sent or received, we want to write - * frames if there is pending ones. If pending frame is request/push - * response HEADERS and concurrent stream limit is reached, we don't - * want to write them. - */ - return session->aob.item || nghttp2_outbound_queue_top(&session->ob_urgent) || - nghttp2_outbound_queue_top(&session->ob_reg) || - (!nghttp2_pq_empty(&session->root.obq) && - session->remote_window_size > 0) || - (nghttp2_outbound_queue_top(&session->ob_syn) && - !session_is_outgoing_concurrent_streams_max(session)); -} - -int nghttp2_session_add_ping(nghttp2_session *session, uint8_t flags, - const uint8_t *opaque_data) { - int rv; - nghttp2_outbound_item *item; - nghttp2_frame *frame; - nghttp2_mem *mem; - - mem = &session->mem; - - if ((flags & NGHTTP2_FLAG_ACK) && - session->obq_flood_counter_ >= NGHTTP2_MAX_OBQ_FLOOD_ITEM) { - return NGHTTP2_ERR_FLOODED; - } - - item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); - if (item == NULL) { - return NGHTTP2_ERR_NOMEM; - } - - nghttp2_outbound_item_init(item); - - frame = &item->frame; - - nghttp2_frame_ping_init(&frame->ping, flags, opaque_data); - - rv = nghttp2_session_add_item(session, item); - - if (rv != 0) { - nghttp2_frame_ping_free(&frame->ping); - nghttp2_mem_free(mem, item); - return rv; - } - - if (flags & NGHTTP2_FLAG_ACK) { - ++session->obq_flood_counter_; - } - - return 0; -} - -int nghttp2_session_add_goaway(nghttp2_session *session, int32_t last_stream_id, - uint32_t error_code, const uint8_t *opaque_data, - size_t opaque_data_len, uint8_t aux_flags) { - int rv; - nghttp2_outbound_item *item; - nghttp2_frame *frame; - uint8_t *opaque_data_copy = NULL; - nghttp2_goaway_aux_data *aux_data; - nghttp2_mem *mem; - - mem = &session->mem; - - if (nghttp2_session_is_my_stream_id(session, last_stream_id)) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - - if (opaque_data_len) { - if (opaque_data_len + 8 > NGHTTP2_MAX_PAYLOADLEN) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - opaque_data_copy = nghttp2_mem_malloc(mem, opaque_data_len); - if (opaque_data_copy == NULL) { - return NGHTTP2_ERR_NOMEM; - } - memcpy(opaque_data_copy, opaque_data, opaque_data_len); - } - - item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); - if (item == NULL) { - nghttp2_mem_free(mem, opaque_data_copy); - return NGHTTP2_ERR_NOMEM; - } - - nghttp2_outbound_item_init(item); - - frame = &item->frame; - - /* last_stream_id must not be increased from the value previously - sent */ - last_stream_id = nghttp2_min(last_stream_id, session->local_last_stream_id); - - nghttp2_frame_goaway_init(&frame->goaway, last_stream_id, error_code, - opaque_data_copy, opaque_data_len); - - aux_data = &item->aux_data.goaway; - aux_data->flags = aux_flags; - - rv = nghttp2_session_add_item(session, item); - if (rv != 0) { - nghttp2_frame_goaway_free(&frame->goaway, mem); - nghttp2_mem_free(mem, item); - return rv; - } - return 0; -} - -int nghttp2_session_add_window_update(nghttp2_session *session, uint8_t flags, - int32_t stream_id, - int32_t window_size_increment) { - int rv; - nghttp2_outbound_item *item; - nghttp2_frame *frame; - nghttp2_mem *mem; - - mem = &session->mem; - item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); - if (item == NULL) { - return NGHTTP2_ERR_NOMEM; - } - - nghttp2_outbound_item_init(item); - - frame = &item->frame; - - nghttp2_frame_window_update_init(&frame->window_update, flags, stream_id, - window_size_increment); - - rv = nghttp2_session_add_item(session, item); - - if (rv != 0) { - nghttp2_frame_window_update_free(&frame->window_update); - nghttp2_mem_free(mem, item); - return rv; - } - return 0; -} - -static void -session_append_inflight_settings(nghttp2_session *session, - nghttp2_inflight_settings *settings) { - nghttp2_inflight_settings **i; - - for (i = &session->inflight_settings_head; *i; i = &(*i)->next) - ; - - *i = settings; -} - -int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags, - const nghttp2_settings_entry *iv, size_t niv) { - nghttp2_outbound_item *item; - nghttp2_frame *frame; - nghttp2_settings_entry *iv_copy; - size_t i; - int rv; - nghttp2_mem *mem; - nghttp2_inflight_settings *inflight_settings = NULL; - - mem = &session->mem; - - if (flags & NGHTTP2_FLAG_ACK) { - if (niv != 0) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - - if (session->obq_flood_counter_ >= NGHTTP2_MAX_OBQ_FLOOD_ITEM) { - return NGHTTP2_ERR_FLOODED; - } - } - - if (!nghttp2_iv_check(iv, niv)) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - - item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); - if (item == NULL) { - return NGHTTP2_ERR_NOMEM; - } - - if (niv > 0) { - iv_copy = nghttp2_frame_iv_copy(iv, niv, mem); - if (iv_copy == NULL) { - nghttp2_mem_free(mem, item); - return NGHTTP2_ERR_NOMEM; - } - } else { - iv_copy = NULL; - } - - if ((flags & NGHTTP2_FLAG_ACK) == 0) { - rv = inflight_settings_new(&inflight_settings, iv, niv, mem); - if (rv != 0) { - assert(nghttp2_is_fatal(rv)); - nghttp2_mem_free(mem, iv_copy); - nghttp2_mem_free(mem, item); - return rv; - } - } - - nghttp2_outbound_item_init(item); - - frame = &item->frame; - - nghttp2_frame_settings_init(&frame->settings, flags, iv_copy, niv); - rv = nghttp2_session_add_item(session, item); - if (rv != 0) { - /* The only expected error is fatal one */ - assert(nghttp2_is_fatal(rv)); - - inflight_settings_del(inflight_settings, mem); - - nghttp2_frame_settings_free(&frame->settings, mem); - nghttp2_mem_free(mem, item); - - return rv; - } - - if (flags & NGHTTP2_FLAG_ACK) { - ++session->obq_flood_counter_; - } else { - session_append_inflight_settings(session, inflight_settings); - } - - /* Extract NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS and ENABLE_PUSH - here. We use it to refuse the incoming stream and PUSH_PROMISE - with RST_STREAM. */ - - for (i = niv; i > 0; --i) { - if (iv[i - 1].settings_id == NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS) { - session->pending_local_max_concurrent_stream = iv[i - 1].value; - break; - } - } - - for (i = niv; i > 0; --i) { - if (iv[i - 1].settings_id == NGHTTP2_SETTINGS_ENABLE_PUSH) { - session->pending_enable_push = (uint8_t)iv[i - 1].value; - break; - } - } - - for (i = niv; i > 0; --i) { - if (iv[i - 1].settings_id == NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL) { - session->pending_enable_connect_protocol = (uint8_t)iv[i - 1].value; - break; - } - } - - return 0; -} - -int nghttp2_session_pack_data(nghttp2_session *session, nghttp2_bufs *bufs, - size_t datamax, nghttp2_frame *frame, - nghttp2_data_aux_data *aux_data, - nghttp2_stream *stream) { - int rv; - uint32_t data_flags; - ssize_t payloadlen; - ssize_t padded_payloadlen; - nghttp2_buf *buf; - size_t max_payloadlen; - - assert(bufs->head == bufs->cur); - - buf = &bufs->cur->buf; - - if (session->callbacks.read_length_callback) { - - payloadlen = session->callbacks.read_length_callback( - session, frame->hd.type, stream->stream_id, session->remote_window_size, - stream->remote_window_size, session->remote_settings.max_frame_size, - session->user_data); - - DEBUGF("send: read_length_callback=%zd\n", payloadlen); - - payloadlen = nghttp2_session_enforce_flow_control_limits(session, stream, - payloadlen); - - DEBUGF("send: read_length_callback after flow control=%zd\n", payloadlen); - - if (payloadlen <= 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - - if ((size_t)payloadlen > nghttp2_buf_avail(buf)) { - /* Resize the current buffer(s). The reason why we do +1 for - buffer size is for possible padding field. */ - rv = nghttp2_bufs_realloc(&session->aob.framebufs, - (size_t)(NGHTTP2_FRAME_HDLEN + 1 + payloadlen)); - - if (rv != 0) { - DEBUGF("send: realloc buffer failed rv=%d", rv); - /* If reallocation failed, old buffers are still in tact. So - use safe limit. */ - payloadlen = (ssize_t)datamax; - - DEBUGF("send: use safe limit payloadlen=%zd", payloadlen); - } else { - assert(&session->aob.framebufs == bufs); - - buf = &bufs->cur->buf; - } - } - datamax = (size_t)payloadlen; - } - - /* Current max DATA length is less then buffer chunk size */ - assert(nghttp2_buf_avail(buf) >= datamax); - - data_flags = NGHTTP2_DATA_FLAG_NONE; - payloadlen = aux_data->data_prd.read_callback( - session, frame->hd.stream_id, buf->pos, datamax, &data_flags, - &aux_data->data_prd.source, session->user_data); - - if (payloadlen == NGHTTP2_ERR_DEFERRED || - payloadlen == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE || - payloadlen == NGHTTP2_ERR_PAUSE) { - DEBUGF("send: DATA postponed due to %s\n", - nghttp2_strerror((int)payloadlen)); - - return (int)payloadlen; - } - - if (payloadlen < 0 || datamax < (size_t)payloadlen) { - /* This is the error code when callback is failed. */ - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - - buf->last = buf->pos + payloadlen; - buf->pos -= NGHTTP2_FRAME_HDLEN; - - /* Clear flags, because this may contain previous flags of previous - DATA */ - frame->hd.flags = NGHTTP2_FLAG_NONE; - - if (data_flags & NGHTTP2_DATA_FLAG_EOF) { - aux_data->eof = 1; - /* If NGHTTP2_DATA_FLAG_NO_END_STREAM is set, don't set - NGHTTP2_FLAG_END_STREAM */ - if ((aux_data->flags & NGHTTP2_FLAG_END_STREAM) && - (data_flags & NGHTTP2_DATA_FLAG_NO_END_STREAM) == 0) { - frame->hd.flags |= NGHTTP2_FLAG_END_STREAM; - } - } - - if (data_flags & NGHTTP2_DATA_FLAG_NO_COPY) { - if (session->callbacks.send_data_callback == NULL) { - DEBUGF("NGHTTP2_DATA_FLAG_NO_COPY requires send_data_callback set\n"); - - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - aux_data->no_copy = 1; - } - - frame->hd.length = (size_t)payloadlen; - frame->data.padlen = 0; - - max_payloadlen = nghttp2_min(datamax, frame->hd.length + NGHTTP2_MAX_PADLEN); - - padded_payloadlen = - session_call_select_padding(session, frame, max_payloadlen); - - if (nghttp2_is_fatal((int)padded_payloadlen)) { - return (int)padded_payloadlen; - } - - frame->data.padlen = (size_t)(padded_payloadlen - payloadlen); - - nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); - - rv = nghttp2_frame_add_pad(bufs, &frame->hd, frame->data.padlen, - aux_data->no_copy); - if (rv != 0) { - return rv; - } - - reschedule_stream(stream); - - if (frame->hd.length == 0 && (data_flags & NGHTTP2_DATA_FLAG_EOF) && - (data_flags & NGHTTP2_DATA_FLAG_NO_END_STREAM)) { - /* DATA payload length is 0, and DATA frame does not bear - END_STREAM. In this case, there is no point to send 0 length - DATA frame. */ - return NGHTTP2_ERR_CANCEL; - } - - return 0; -} - -void *nghttp2_session_get_stream_user_data(nghttp2_session *session, - int32_t stream_id) { - nghttp2_stream *stream; - stream = nghttp2_session_get_stream(session, stream_id); - if (stream) { - return stream->stream_user_data; - } else { - return NULL; - } -} - -int nghttp2_session_set_stream_user_data(nghttp2_session *session, - int32_t stream_id, - void *stream_user_data) { - nghttp2_stream *stream; - nghttp2_frame *frame; - nghttp2_outbound_item *item; - - stream = nghttp2_session_get_stream(session, stream_id); - if (stream) { - stream->stream_user_data = stream_user_data; - return 0; - } - - if (session->server || !nghttp2_session_is_my_stream_id(session, stream_id) || - !nghttp2_outbound_queue_top(&session->ob_syn)) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - - frame = &nghttp2_outbound_queue_top(&session->ob_syn)->frame; - assert(frame->hd.type == NGHTTP2_HEADERS); - - if (frame->hd.stream_id > stream_id || - (uint32_t)stream_id >= session->next_stream_id) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - - for (item = session->ob_syn.head; item; item = item->qnext) { - if (item->frame.hd.stream_id < stream_id) { - continue; - } - - if (item->frame.hd.stream_id > stream_id) { - break; - } - - item->aux_data.headers.stream_user_data = stream_user_data; - return 0; - } - - return NGHTTP2_ERR_INVALID_ARGUMENT; -} - -int nghttp2_session_resume_data(nghttp2_session *session, int32_t stream_id) { - int rv; - nghttp2_stream *stream; - stream = nghttp2_session_get_stream(session, stream_id); - if (stream == NULL || !nghttp2_stream_check_deferred_item(stream)) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - - rv = nghttp2_stream_resume_deferred_item(stream, - NGHTTP2_STREAM_FLAG_DEFERRED_USER); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - return 0; -} - -size_t nghttp2_session_get_outbound_queue_size(nghttp2_session *session) { - return nghttp2_outbound_queue_size(&session->ob_urgent) + - nghttp2_outbound_queue_size(&session->ob_reg) + - nghttp2_outbound_queue_size(&session->ob_syn); - /* TODO account for item attached to stream */ -} - -int32_t -nghttp2_session_get_stream_effective_recv_data_length(nghttp2_session *session, - int32_t stream_id) { - nghttp2_stream *stream; - stream = nghttp2_session_get_stream(session, stream_id); - if (stream == NULL) { - return -1; - } - return stream->recv_window_size < 0 ? 0 : stream->recv_window_size; -} - -int32_t -nghttp2_session_get_stream_effective_local_window_size(nghttp2_session *session, - int32_t stream_id) { - nghttp2_stream *stream; - stream = nghttp2_session_get_stream(session, stream_id); - if (stream == NULL) { - return -1; - } - return stream->local_window_size; -} - -int32_t nghttp2_session_get_stream_local_window_size(nghttp2_session *session, - int32_t stream_id) { - nghttp2_stream *stream; - int32_t size; - stream = nghttp2_session_get_stream(session, stream_id); - if (stream == NULL) { - return -1; - } - - size = stream->local_window_size - stream->recv_window_size; - - /* size could be negative if local endpoint reduced - SETTINGS_INITIAL_WINDOW_SIZE */ - if (size < 0) { - return 0; - } - - return size; -} - -int32_t -nghttp2_session_get_effective_recv_data_length(nghttp2_session *session) { - return session->recv_window_size < 0 ? 0 : session->recv_window_size; -} - -int32_t -nghttp2_session_get_effective_local_window_size(nghttp2_session *session) { - return session->local_window_size; -} - -int32_t nghttp2_session_get_local_window_size(nghttp2_session *session) { - return session->local_window_size - session->recv_window_size; -} - -int32_t nghttp2_session_get_stream_remote_window_size(nghttp2_session *session, - int32_t stream_id) { - nghttp2_stream *stream; - - stream = nghttp2_session_get_stream(session, stream_id); - if (stream == NULL) { - return -1; - } - - /* stream->remote_window_size can be negative when - SETTINGS_INITIAL_WINDOW_SIZE is changed. */ - return nghttp2_max(0, stream->remote_window_size); -} - -int32_t nghttp2_session_get_remote_window_size(nghttp2_session *session) { - return session->remote_window_size; -} - -uint32_t nghttp2_session_get_remote_settings(nghttp2_session *session, - nghttp2_settings_id id) { - switch (id) { - case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: - return session->remote_settings.header_table_size; - case NGHTTP2_SETTINGS_ENABLE_PUSH: - return session->remote_settings.enable_push; - case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: - return session->remote_settings.max_concurrent_streams; - case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: - return session->remote_settings.initial_window_size; - case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: - return session->remote_settings.max_frame_size; - case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: - return session->remote_settings.max_header_list_size; - case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: - return session->remote_settings.enable_connect_protocol; - } - - assert(0); - abort(); /* if NDEBUG is set */ -} - -uint32_t nghttp2_session_get_local_settings(nghttp2_session *session, - nghttp2_settings_id id) { - switch (id) { - case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: - return session->local_settings.header_table_size; - case NGHTTP2_SETTINGS_ENABLE_PUSH: - return session->local_settings.enable_push; - case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: - return session->local_settings.max_concurrent_streams; - case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: - return session->local_settings.initial_window_size; - case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: - return session->local_settings.max_frame_size; - case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: - return session->local_settings.max_header_list_size; - case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: - return session->local_settings.enable_connect_protocol; - } - - assert(0); - abort(); /* if NDEBUG is set */ -} - -static int nghttp2_session_upgrade_internal(nghttp2_session *session, - const uint8_t *settings_payload, - size_t settings_payloadlen, - void *stream_user_data) { - nghttp2_stream *stream; - nghttp2_frame frame; - nghttp2_settings_entry *iv; - size_t niv; - int rv; - nghttp2_priority_spec pri_spec; - nghttp2_mem *mem; - - mem = &session->mem; - - if ((!session->server && session->next_stream_id != 1) || - (session->server && session->last_recv_stream_id >= 1)) { - return NGHTTP2_ERR_PROTO; - } - if (settings_payloadlen % NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - rv = nghttp2_frame_unpack_settings_payload2(&iv, &niv, settings_payload, - settings_payloadlen, mem); - if (rv != 0) { - return rv; - } - - if (session->server) { - nghttp2_frame_hd_init(&frame.hd, settings_payloadlen, NGHTTP2_SETTINGS, - NGHTTP2_FLAG_NONE, 0); - frame.settings.iv = iv; - frame.settings.niv = niv; - rv = nghttp2_session_on_settings_received(session, &frame, 1 /* No ACK */); - } else { - rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, niv); - } - nghttp2_mem_free(mem, iv); - if (rv != 0) { - return rv; - } - - nghttp2_priority_spec_default_init(&pri_spec); - - stream = nghttp2_session_open_stream( - session, 1, NGHTTP2_STREAM_FLAG_NONE, &pri_spec, NGHTTP2_STREAM_OPENING, - session->server ? NULL : stream_user_data); - if (stream == NULL) { - return NGHTTP2_ERR_NOMEM; - } - - /* We don't call nghttp2_session_adjust_closed_stream(), since this - should be the first stream open. */ - - if (session->server) { - nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); - session->last_recv_stream_id = 1; - session->last_proc_stream_id = 1; - } else { - nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); - session->last_sent_stream_id = 1; - session->next_stream_id += 2; - } - return 0; -} - -int nghttp2_session_upgrade(nghttp2_session *session, - const uint8_t *settings_payload, - size_t settings_payloadlen, - void *stream_user_data) { - int rv; - nghttp2_stream *stream; - - rv = nghttp2_session_upgrade_internal(session, settings_payload, - settings_payloadlen, stream_user_data); - if (rv != 0) { - return rv; - } - - stream = nghttp2_session_get_stream(session, 1); - assert(stream); - - /* We have no information about request header fields when Upgrade - was happened. So we don't know the request method here. If - request method is HEAD, we have a trouble because we may have - nonzero content-length header field in response headers, and we - will going to check it against the actual DATA frames, but we may - get mismatch because HEAD response body must be empty. Because - of this reason, nghttp2_session_upgrade() was deprecated in favor - of nghttp2_session_upgrade2(), which has |head_request| parameter - to indicate that request method is HEAD or not. */ - stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND; - return 0; -} - -int nghttp2_session_upgrade2(nghttp2_session *session, - const uint8_t *settings_payload, - size_t settings_payloadlen, int head_request, - void *stream_user_data) { - int rv; - nghttp2_stream *stream; - - rv = nghttp2_session_upgrade_internal(session, settings_payload, - settings_payloadlen, stream_user_data); - if (rv != 0) { - return rv; - } - - stream = nghttp2_session_get_stream(session, 1); - assert(stream); - - if (head_request) { - stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD; - } - - return 0; -} - -int nghttp2_session_get_stream_local_close(nghttp2_session *session, - int32_t stream_id) { - nghttp2_stream *stream; - - stream = nghttp2_session_get_stream(session, stream_id); - - if (!stream) { - return -1; - } - - return (stream->shut_flags & NGHTTP2_SHUT_WR) != 0; -} - -int nghttp2_session_get_stream_remote_close(nghttp2_session *session, - int32_t stream_id) { - nghttp2_stream *stream; - - stream = nghttp2_session_get_stream(session, stream_id); - - if (!stream) { - return -1; - } - - return (stream->shut_flags & NGHTTP2_SHUT_RD) != 0; -} - -int nghttp2_session_consume(nghttp2_session *session, int32_t stream_id, - size_t size) { - int rv; - nghttp2_stream *stream; - - if (stream_id == 0) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - - if (!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { - return NGHTTP2_ERR_INVALID_STATE; - } - - rv = session_update_connection_consumed_size(session, size); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - stream = nghttp2_session_get_stream(session, stream_id); - - if (!stream) { - return 0; - } - - rv = session_update_stream_consumed_size(session, stream, size); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - return 0; -} - -int nghttp2_session_consume_connection(nghttp2_session *session, size_t size) { - int rv; - - if (!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { - return NGHTTP2_ERR_INVALID_STATE; - } - - rv = session_update_connection_consumed_size(session, size); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - return 0; -} - -int nghttp2_session_consume_stream(nghttp2_session *session, int32_t stream_id, - size_t size) { - int rv; - nghttp2_stream *stream; - - if (stream_id == 0) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - - if (!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { - return NGHTTP2_ERR_INVALID_STATE; - } - - stream = nghttp2_session_get_stream(session, stream_id); - - if (!stream) { - return 0; - } - - rv = session_update_stream_consumed_size(session, stream, size); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - return 0; -} - -int nghttp2_session_set_next_stream_id(nghttp2_session *session, - int32_t next_stream_id) { - if (next_stream_id <= 0 || - session->next_stream_id > (uint32_t)next_stream_id) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - - if (session->server) { - if (next_stream_id % 2) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - } else if (next_stream_id % 2 == 0) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - - session->next_stream_id = (uint32_t)next_stream_id; - return 0; -} - -uint32_t nghttp2_session_get_next_stream_id(nghttp2_session *session) { - return session->next_stream_id; -} - -int32_t nghttp2_session_get_last_proc_stream_id(nghttp2_session *session) { - return session->last_proc_stream_id; -} - -nghttp2_stream *nghttp2_session_find_stream(nghttp2_session *session, - int32_t stream_id) { - if (stream_id == 0) { - return &session->root; - } - - return nghttp2_session_get_stream_raw(session, stream_id); -} - -nghttp2_stream *nghttp2_session_get_root_stream(nghttp2_session *session) { - return &session->root; -} - -int nghttp2_session_check_server_session(nghttp2_session *session) { - return session->server; -} - -int nghttp2_session_change_stream_priority( - nghttp2_session *session, int32_t stream_id, - const nghttp2_priority_spec *pri_spec) { - int rv; - nghttp2_stream *stream; - nghttp2_priority_spec pri_spec_copy; - - if (stream_id == 0 || stream_id == pri_spec->stream_id) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - - stream = nghttp2_session_get_stream_raw(session, stream_id); - if (!stream) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - - pri_spec_copy = *pri_spec; - nghttp2_priority_spec_normalize_weight(&pri_spec_copy); - - rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec_copy); - - if (nghttp2_is_fatal(rv)) { - return rv; - } - - /* We don't intentionally call nghttp2_session_adjust_idle_stream() - so that idle stream created by this function, and existing ones - are kept for application. We will adjust number of idle stream - in nghttp2_session_mem_send or nghttp2_session_mem_recv is - called. */ - return 0; -} - -int nghttp2_session_create_idle_stream(nghttp2_session *session, - int32_t stream_id, - const nghttp2_priority_spec *pri_spec) { - nghttp2_stream *stream; - nghttp2_priority_spec pri_spec_copy; - - if (stream_id == 0 || stream_id == pri_spec->stream_id || - !session_detect_idle_stream(session, stream_id)) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - - stream = nghttp2_session_get_stream_raw(session, stream_id); - if (stream) { - return NGHTTP2_ERR_INVALID_ARGUMENT; - } - - pri_spec_copy = *pri_spec; - nghttp2_priority_spec_normalize_weight(&pri_spec_copy); - - stream = - nghttp2_session_open_stream(session, stream_id, NGHTTP2_STREAM_FLAG_NONE, - &pri_spec_copy, NGHTTP2_STREAM_IDLE, NULL); - if (!stream) { - return NGHTTP2_ERR_NOMEM; - } - - /* We don't intentionally call nghttp2_session_adjust_idle_stream() - so that idle stream created by this function, and existing ones - are kept for application. We will adjust number of idle stream - in nghttp2_session_mem_send or nghttp2_session_mem_recv is - called. */ - return 0; -} - -size_t -nghttp2_session_get_hd_inflate_dynamic_table_size(nghttp2_session *session) { - return nghttp2_hd_inflate_get_dynamic_table_size(&session->hd_inflater); -} - -size_t -nghttp2_session_get_hd_deflate_dynamic_table_size(nghttp2_session *session) { - return nghttp2_hd_deflate_get_dynamic_table_size(&session->hd_deflater); -} - -void nghttp2_session_set_user_data(nghttp2_session *session, void *user_data) { - session->user_data = user_data; -} |