nghttp2/lib/nghttp2_session.c

3186 lines
108 KiB
C
Raw Normal View History

/*
2013-07-12 17:19:03 +02:00
* nghttp2 - HTTP/2.0 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.
*/
2013-07-12 17:19:03 +02:00
#include "nghttp2_session.h"
#include <string.h>
#include <stddef.h>
#include <stdio.h>
#include <assert.h>
2013-07-12 17:19:03 +02:00
#include "nghttp2_helper.h"
#include "nghttp2_net.h"
/*
* Returns non-zero if the number of outgoing opened streams is larger
* than or equal to
2013-07-12 17:19:03 +02:00
* remote_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS].
*/
2013-07-12 17:19:03 +02:00
static int nghttp2_session_is_outgoing_concurrent_streams_max
(nghttp2_session *session)
{
2013-07-12 17:19:03 +02:00
return session->remote_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS]
<= session->num_outgoing_streams;
}
/*
* Returns non-zero if the number of incoming opened streams is larger
* than or equal to
2013-07-12 17:19:03 +02:00
* local_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS].
*/
2013-07-12 17:19:03 +02:00
static int nghttp2_session_is_incoming_concurrent_streams_max
(nghttp2_session *session)
{
2013-07-12 17:19:03 +02:00
return session->local_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS]
<= session->num_incoming_streams;
}
/*
* Returns non-zero if |error| is non-fatal error.
*/
2013-07-12 17:19:03 +02:00
static int nghttp2_is_non_fatal(int error)
{
2013-07-12 17:19:03 +02:00
return error < 0 && error > NGHTTP2_ERR_FATAL;
}
2012-03-07 16:40:17 +01:00
/*
* Returns non-zero if |error| is fatal error.
*/
2013-07-12 17:19:03 +02:00
static int nghttp2_is_fatal(int error)
2012-03-07 16:40:17 +01:00
{
2013-07-12 17:19:03 +02:00
return error < NGHTTP2_ERR_FATAL;
2012-03-07 16:40:17 +01:00
}
2013-07-24 18:49:05 +02:00
/* Returns the pushed stream's priority based on the associated stream
|stream|. */
static int32_t nghttp2_pushed_stream_pri(nghttp2_stream *stream)
{
return stream->pri == NGHTTP2_PRI_LOWEST ?
(int32_t)NGHTTP2_PRI_LOWEST : stream->pri + 1;
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_fail_session(nghttp2_session *session,
2013-07-15 14:45:59 +02:00
nghttp2_error_code error_code)
{
2013-07-12 17:19:03 +02:00
session->goaway_flags |= NGHTTP2_GOAWAY_FAIL_ON_SEND;
2013-07-15 14:45:59 +02:00
return nghttp2_submit_goaway(session, error_code, NULL, 0);
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_is_my_stream_id(nghttp2_session *session,
int32_t stream_id)
{
int r;
if(stream_id == 0) {
return 0;
}
r = stream_id % 2;
return (session->server && r == 0) || (!session->server && r == 1);
}
2013-07-12 17:19:03 +02:00
nghttp2_stream* nghttp2_session_get_stream(nghttp2_session *session,
int32_t stream_id)
{
2013-07-12 17:19:03 +02:00
return (nghttp2_stream*)nghttp2_map_find(&session->streams, stream_id);
}
2013-07-12 17:19:03 +02:00
static int nghttp2_outbound_item_compar(const void *lhsx, const void *rhsx)
{
2013-07-12 17:19:03 +02:00
const nghttp2_outbound_item *lhs, *rhs;
lhs = (const nghttp2_outbound_item*)lhsx;
rhs = (const nghttp2_outbound_item*)rhsx;
if(lhs->pri == rhs->pri) {
return (lhs->seq < rhs->seq) ? -1 : ((lhs->seq > rhs->seq) ? 1 : 0);
} else {
2013-07-15 14:45:59 +02:00
return lhs->pri - rhs->pri;
}
}
2013-07-12 17:19:03 +02:00
static void nghttp2_inbound_frame_reset(nghttp2_inbound_frame *iframe)
{
2013-07-12 17:19:03 +02:00
iframe->state = NGHTTP2_RECV_HEAD;
iframe->payloadlen = iframe->buflen = iframe->off = 0;
iframe->headbufoff = 0;
iframe->error_code = 0;
}
2013-07-12 17:19:03 +02:00
static int nghttp2_session_new(nghttp2_session **session_ptr,
const nghttp2_session_callbacks *callbacks,
2012-04-05 18:45:39 +02:00
void *user_data,
2013-07-19 17:08:14 +02:00
nghttp2_hd_side side)
{
int r;
2013-07-12 17:19:03 +02:00
*session_ptr = malloc(sizeof(nghttp2_session));
if(*session_ptr == NULL) {
2013-07-12 17:19:03 +02:00
r = NGHTTP2_ERR_NOMEM;
goto fail_session;
}
2013-07-12 17:19:03 +02:00
memset(*session_ptr, 0, sizeof(nghttp2_session));
2012-01-27 15:05:29 +01:00
2013-07-15 14:45:59 +02:00
/* next_stream_id and last_recv_stream_id are initialized in either
nghttp2_session_client_new or nghttp2_session_server_new */
(*session_ptr)->next_seq = 0;
2013-07-15 14:45:59 +02:00
(*session_ptr)->remote_flow_control = 1;
(*session_ptr)->local_flow_control = 1;
(*session_ptr)->remote_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
2013-07-15 14:45:59 +02:00
(*session_ptr)->recv_window_size = 0;
(*session_ptr)->local_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
2013-07-15 14:45:59 +02:00
2013-07-12 17:19:03 +02:00
(*session_ptr)->goaway_flags = NGHTTP2_GOAWAY_NONE;
2013-07-15 14:45:59 +02:00
(*session_ptr)->last_stream_id = 0;
2012-01-28 11:22:38 +01:00
2013-07-19 17:08:14 +02:00
r = nghttp2_hd_deflate_init(&(*session_ptr)->hd_deflater, side);
if(r != 0) {
goto fail_hd_deflater;
}
2013-07-19 17:08:14 +02:00
r = nghttp2_hd_inflate_init(&(*session_ptr)->hd_inflater, side);
if(r != 0) {
goto fail_hd_inflater;
}
2013-07-12 17:19:03 +02:00
nghttp2_map_init(&(*session_ptr)->streams);
r = nghttp2_pq_init(&(*session_ptr)->ob_pq, nghttp2_outbound_item_compar);
if(r != 0) {
goto fail_ob_pq;
}
2013-07-12 17:19:03 +02:00
r = nghttp2_pq_init(&(*session_ptr)->ob_ss_pq, nghttp2_outbound_item_compar);
if(r != 0) {
goto fail_ob_ss_pq;
}
(*session_ptr)->aob.framebuf = malloc
2013-07-12 17:19:03 +02:00
(NGHTTP2_INITIAL_OUTBOUND_FRAMEBUF_LENGTH);
if((*session_ptr)->aob.framebuf == NULL) {
2013-07-12 17:19:03 +02:00
r = NGHTTP2_ERR_NOMEM;
goto fail_aob_framebuf;
}
2013-07-12 17:19:03 +02:00
(*session_ptr)->aob.framebufmax = NGHTTP2_INITIAL_OUTBOUND_FRAMEBUF_LENGTH;
2013-07-12 17:19:03 +02:00
(*session_ptr)->nvbuf = malloc(NGHTTP2_INITIAL_NV_BUFFER_LENGTH);
if((*session_ptr)->nvbuf == NULL) {
2013-07-12 17:19:03 +02:00
r = NGHTTP2_ERR_NOMEM;
goto fail_nvbuf;
}
2013-07-12 17:19:03 +02:00
(*session_ptr)->nvbuflen = NGHTTP2_INITIAL_NV_BUFFER_LENGTH;
memset((*session_ptr)->remote_settings, 0,
sizeof((*session_ptr)->remote_settings));
2013-07-12 17:19:03 +02:00
(*session_ptr)->remote_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] =
NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
(*session_ptr)->remote_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE] =
NGHTTP2_INITIAL_WINDOW_SIZE;
memset((*session_ptr)->local_settings, 0,
sizeof((*session_ptr)->local_settings));
2013-07-12 17:19:03 +02:00
(*session_ptr)->local_settings[NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] =
NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
(*session_ptr)->local_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE] =
NGHTTP2_INITIAL_WINDOW_SIZE;
2012-02-25 16:12:32 +01:00
(*session_ptr)->callbacks = *callbacks;
(*session_ptr)->user_data = user_data;
2013-07-12 17:19:03 +02:00
(*session_ptr)->iframe.buf = malloc(NGHTTP2_INITIAL_INBOUND_FRAMEBUF_LENGTH);
if((*session_ptr)->iframe.buf == NULL) {
2013-07-12 17:19:03 +02:00
r = NGHTTP2_ERR_NOMEM;
goto fail_iframe_buf;
}
2013-07-12 17:19:03 +02:00
(*session_ptr)->iframe.bufmax = NGHTTP2_INITIAL_INBOUND_FRAMEBUF_LENGTH;
2013-07-12 17:19:03 +02:00
nghttp2_inbound_frame_reset(&(*session_ptr)->iframe);
return 0;
fail_iframe_buf:
free((*session_ptr)->nvbuf);
fail_nvbuf:
free((*session_ptr)->aob.framebuf);
fail_aob_framebuf:
2013-07-12 17:19:03 +02:00
nghttp2_pq_free(&(*session_ptr)->ob_ss_pq);
fail_ob_ss_pq:
2013-07-12 17:19:03 +02:00
nghttp2_pq_free(&(*session_ptr)->ob_pq);
fail_ob_pq:
/* No need to free (*session_ptr)->streams) here. */
2013-07-19 17:08:14 +02:00
nghttp2_hd_inflate_free(&(*session_ptr)->hd_inflater);
fail_hd_inflater:
2013-07-19 17:08:14 +02:00
nghttp2_hd_deflate_free(&(*session_ptr)->hd_deflater);
fail_hd_deflater:
free(*session_ptr);
fail_session:
return r;
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_client_new(nghttp2_session **session_ptr,
const nghttp2_session_callbacks *callbacks,
void *user_data)
{
int r;
/* For client side session, header compression is disabled. */
2013-07-19 17:08:14 +02:00
r = nghttp2_session_new(session_ptr, callbacks, user_data,
NGHTTP2_HD_SIDE_CLIENT);
if(r == 0) {
/* IDs for use in client */
(*session_ptr)->next_stream_id = 1;
(*session_ptr)->last_recv_stream_id = 0;
}
return r;
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_server_new(nghttp2_session **session_ptr,
const nghttp2_session_callbacks *callbacks,
void *user_data)
{
int r;
/* Enable header compression on server side. */
2013-07-19 17:08:14 +02:00
r = nghttp2_session_new(session_ptr, callbacks, user_data,
NGHTTP2_HD_SIDE_SERVER);
if(r == 0) {
(*session_ptr)->server = 1;
/* IDs for use in client */
(*session_ptr)->next_stream_id = 2;
(*session_ptr)->last_recv_stream_id = 0;
}
return r;
}
2013-07-12 17:19:03 +02:00
static int nghttp2_free_streams(nghttp2_map_entry *entry, void *ptr)
{
2013-07-12 17:19:03 +02:00
nghttp2_stream_free((nghttp2_stream*)entry);
free(entry);
return 0;
}
2013-07-12 17:19:03 +02:00
static void nghttp2_session_ob_pq_free(nghttp2_pq *pq)
{
2013-07-12 17:19:03 +02:00
while(!nghttp2_pq_empty(pq)) {
nghttp2_outbound_item *item = (nghttp2_outbound_item*)nghttp2_pq_top(pq);
nghttp2_outbound_item_free(item);
free(item);
2013-07-12 17:19:03 +02:00
nghttp2_pq_pop(pq);
}
2013-07-12 17:19:03 +02:00
nghttp2_pq_free(pq);
}
2013-07-12 17:19:03 +02:00
static void nghttp2_active_outbound_item_reset
(nghttp2_active_outbound_item *aob)
{
2013-07-12 17:19:03 +02:00
nghttp2_outbound_item_free(aob->item);
free(aob->item);
aob->item = NULL;
aob->framebuflen = aob->framebufoff = 0;
}
2013-07-12 17:19:03 +02:00
void nghttp2_session_del(nghttp2_session *session)
{
if(session == NULL) {
return;
}
2013-07-12 17:19:03 +02:00
nghttp2_map_each_free(&session->streams, nghttp2_free_streams, NULL);
nghttp2_session_ob_pq_free(&session->ob_pq);
nghttp2_session_ob_pq_free(&session->ob_ss_pq);
2013-07-19 17:08:14 +02:00
nghttp2_hd_deflate_free(&session->hd_deflater);
nghttp2_hd_inflate_free(&session->hd_inflater);
2013-07-12 17:19:03 +02:00
nghttp2_active_outbound_item_reset(&session->aob);
free(session->aob.framebuf);
free(session->nvbuf);
free(session->iframe.buf);
2013-07-15 14:45:59 +02:00
free(session);
}
static int outbound_item_update_pri
(nghttp2_outbound_item *item, nghttp2_stream *stream)
{
if(item->frame_cat == NGHTTP2_CAT_CTRL) {
if(((nghttp2_frame*)item->frame)->hd.stream_id != stream->stream_id) {
return 0;
}
switch(((nghttp2_frame*)item->frame)->hd.type) {
case NGHTTP2_HEADERS:
case NGHTTP2_PUSH_PROMISE:
break;
default:
return 0;
}
} else {
if(((nghttp2_data*)item->frame)->hd.stream_id != stream->stream_id) {
return 0;
}
}
item->pri = stream->pri;
return 1;
}
static int update_stream_pri(void *ptr, void *arg)
{
nghttp2_outbound_item *item = (nghttp2_outbound_item*)ptr;
nghttp2_stream *stream = (nghttp2_stream*)arg;
return outbound_item_update_pri(item, stream);
}
void nghttp2_session_reprioritize_stream
(nghttp2_session *session, nghttp2_stream *stream, int32_t pri)
{
if(stream->pri == pri) {
return;
}
stream->pri = pri;
nghttp2_pq_update(&session->ob_pq, update_stream_pri, stream);
nghttp2_pq_update(&session->ob_ss_pq, update_stream_pri, stream);
if(stream->deferred_data) {
stream->deferred_data->pri = pri;
}
if(session->aob.item) {
outbound_item_update_pri(session->aob.item, stream);
}
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_add_frame(nghttp2_session *session,
nghttp2_frame_category frame_cat,
void *abs_frame,
void *aux_data)
{
/* TODO Return error if stream is not found for the frame requiring
stream presence. */
int r = 0;
2013-07-12 17:19:03 +02:00
nghttp2_outbound_item *item;
item = malloc(sizeof(nghttp2_outbound_item));
if(item == NULL) {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_NOMEM;
}
item->frame_cat = frame_cat;
item->frame = abs_frame;
item->aux_data = aux_data;
item->seq = session->next_seq++;
2013-07-15 14:45:59 +02:00
/* Set priority to the default value at the moment. */
item->pri = NGHTTP2_PRI_DEFAULT;
if(frame_cat == NGHTTP2_CAT_CTRL) {
2013-07-12 17:19:03 +02:00
nghttp2_frame *frame = (nghttp2_frame*)abs_frame;
2013-07-24 18:49:05 +02:00
nghttp2_stream *stream = NULL;
2013-07-15 14:45:59 +02:00
switch(frame->hd.type) {
case NGHTTP2_HEADERS:
if(frame->hd.stream_id == -1) {
/* Initial HEADERS, which will open stream */
item->pri = frame->headers.pri;
} else {
/* Otherwise, the frame must have stream ID. We use its
priority value. */
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if(stream) {
item->pri = stream->pri;
}
}
break;
case NGHTTP2_PRIORITY:
item->pri = -1;
break;
case NGHTTP2_RST_STREAM:
2013-07-15 14:45:59 +02:00
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if(stream) {
2013-07-12 17:19:03 +02:00
stream->state = NGHTTP2_STREAM_CLOSING;
}
item->pri = -1;
break;
2013-07-12 17:19:03 +02:00
case NGHTTP2_SETTINGS:
item->pri = NGHTTP2_OB_PRI_SETTINGS;
break;
case NGHTTP2_PUSH_PROMISE:
2013-07-24 18:49:05 +02:00
/* Use priority of associated stream */
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if(stream) {
item->pri = stream->pri;
}
break;
2013-07-12 17:19:03 +02:00
case NGHTTP2_PING:
2012-04-05 18:45:39 +02:00
/* Ping has highest priority. */
2013-07-12 17:19:03 +02:00
item->pri = NGHTTP2_OB_PRI_PING;
break;
2013-07-12 17:19:03 +02:00
case NGHTTP2_GOAWAY:
/* Should GOAWAY have higher priority? */
break;
2013-07-15 14:45:59 +02:00
case NGHTTP2_WINDOW_UPDATE:
item->pri = -1;
break;
2012-02-23 16:02:29 +01:00
}
2013-07-24 18:49:05 +02:00
if(frame->hd.type == NGHTTP2_HEADERS &&
(frame->hd.stream_id == -1 ||
(stream && stream->state == NGHTTP2_STREAM_RESERVED &&
session->server))) {
/* TODO If 2 HEADERS are submitted for reserved stream, then
both of them are queued into ob_ss_pq, which is not
desirable. */
2013-07-12 17:19:03 +02:00
r = nghttp2_pq_push(&session->ob_ss_pq, item);
} else {
2013-07-12 17:19:03 +02:00
r = nghttp2_pq_push(&session->ob_pq, item);
}
2013-07-15 14:45:59 +02:00
} else if(frame_cat == NGHTTP2_CAT_DATA) {
2013-07-12 17:19:03 +02:00
nghttp2_data *data_frame = (nghttp2_data*)abs_frame;
2013-07-15 14:45:59 +02:00
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, data_frame->hd.stream_id);
if(stream) {
item->pri = stream->pri;
}
2013-07-12 17:19:03 +02:00
r = nghttp2_pq_push(&session->ob_pq, item);
} else {
/* Unreachable */
assert(0);
}
if(r != 0) {
free(item);
return r;
}
return 0;
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_add_rst_stream(nghttp2_session *session,
2013-07-15 14:45:59 +02:00
int32_t stream_id,
nghttp2_error_code error_code)
{
int r;
2013-07-12 17:19:03 +02:00
nghttp2_frame *frame;
frame = malloc(sizeof(nghttp2_frame));
if(frame == NULL) {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_NOMEM;
}
2013-07-15 14:45:59 +02:00
nghttp2_frame_rst_stream_init(&frame->rst_stream, stream_id, error_code);
r = nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL);
if(r != 0) {
2013-07-12 17:19:03 +02:00
nghttp2_frame_rst_stream_free(&frame->rst_stream);
free(frame);
return r;
}
return 0;
}
2013-07-12 17:19:03 +02:00
nghttp2_stream* nghttp2_session_open_stream(nghttp2_session *session,
int32_t stream_id,
2013-07-15 14:45:59 +02:00
uint8_t flags, int32_t pri,
2013-07-12 17:19:03 +02:00
nghttp2_stream_state initial_state,
void *stream_user_data)
{
int r;
2013-07-12 17:19:03 +02:00
nghttp2_stream *stream = malloc(sizeof(nghttp2_stream));
if(stream == NULL) {
return NULL;
}
2013-07-12 17:19:03 +02:00
nghttp2_stream_init(stream, stream_id, flags, pri, initial_state,
2013-07-15 14:45:59 +02:00
!session->remote_settings
[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS],
!session->local_settings
[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS],
session->remote_settings
2013-07-12 17:19:03 +02:00
[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE],
session->local_settings
[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE],
stream_user_data);
2013-07-12 17:19:03 +02:00
r = nghttp2_map_insert(&session->streams, &stream->map_entry);
if(r != 0) {
free(stream);
2013-07-24 18:49:05 +02:00
return NULL;
}
2013-07-24 18:49:05 +02:00
if(initial_state == NGHTTP2_STREAM_RESERVED) {
if(nghttp2_session_is_my_stream_id(session, stream_id)) {
/* half closed (remote) */
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
} else {
/* half closed (local) */
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
}
/* Reserved stream does not count in the concurrent streams
limit. That is one of the DOS vector. */
} else {
2013-07-24 18:49:05 +02:00
if(nghttp2_session_is_my_stream_id(session, stream_id)) {
++session->num_outgoing_streams;
} else {
++session->num_incoming_streams;
}
}
return stream;
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id,
2013-07-15 14:45:59 +02:00
nghttp2_error_code error_code)
{
2013-07-12 17:19:03 +02:00
nghttp2_stream *stream = nghttp2_session_get_stream(session, stream_id);
if(stream) {
2013-07-12 17:19:03 +02:00
if(stream->state != NGHTTP2_STREAM_INITIAL &&
2013-07-24 18:49:05 +02:00
stream->state != NGHTTP2_STREAM_RESERVED &&
/* TODO Should on_stream_close_callback be called against
NGHTTP2_STREAM_RESERVED? It is actually not opened yet. */
2012-01-29 15:00:33 +01:00
session->callbacks.on_stream_close_callback) {
session->callbacks.on_stream_close_callback(session, stream_id,
2013-07-15 14:45:59 +02:00
error_code,
2012-01-29 15:00:33 +01:00
session->user_data);
}
2013-07-24 18:49:05 +02:00
if(stream->state != NGHTTP2_STREAM_RESERVED) {
if(nghttp2_session_is_my_stream_id(session, stream_id)) {
--session->num_outgoing_streams;
} else {
--session->num_incoming_streams;
}
}
2013-07-12 17:19:03 +02:00
nghttp2_map_remove(&session->streams, stream_id);
nghttp2_stream_free(stream);
free(stream);
return 0;
} else {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_close_stream_if_shut_rdwr(nghttp2_session *session,
nghttp2_stream *stream)
{
2013-07-12 17:19:03 +02:00
if((stream->shut_flags & NGHTTP2_SHUT_RDWR) == NGHTTP2_SHUT_RDWR) {
return nghttp2_session_close_stream(session, stream->stream_id,
2013-07-15 14:45:59 +02:00
NGHTTP2_NO_ERROR);
} else {
return 0;
}
}
2013-07-15 14:45:59 +02:00
/*
* 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.
*/
2013-07-12 17:19:03 +02:00
static int nghttp2_predicate_stream_for_send(nghttp2_stream *stream)
{
if(stream == NULL) {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_STREAM_CLOSED;
} else if(stream->shut_flags & NGHTTP2_SHUT_WR) {
return NGHTTP2_ERR_STREAM_SHUT_WR;
} else {
return 0;
}
}
/*
2013-07-15 14:45:59 +02:00
* This function checks HEADERS frame |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:
*
2013-07-15 14:45:59 +02:00
* NGHTTP2_ERR_START_STREAM_NOT_ALLOWED
* New stream cannot be created because GOAWAY is already sent or
* received.
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE
2012-03-15 14:39:26 +01:00
* Stream ID has reached the maximum value. Therefore no stream ID
* is available.
*/
2013-07-25 14:07:38 +02:00
static int nghttp2_session_predicate_request_headers_send
2013-07-15 14:45:59 +02:00
(nghttp2_session *session, nghttp2_headers *frame)
{
if(session->goaway_flags) {
2013-07-25 14:18:13 +02:00
/* When GOAWAY is sent or received, peer must not send new request
HEADERS. */
2013-07-15 14:45:59 +02:00
return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
}
/* All 32bit signed stream IDs are spent. */
if(session->next_stream_id > INT32_MAX) {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE;
}
return 0;
}
/*
2013-07-15 14:45:59 +02:00
* This function checks HEADERS, which is the first frame from the
* server, with the stream ID |stream_id| can be sent at this time.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_STREAM_CLOSED
* The stream is already closed or does not exist.
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_STREAM_SHUT_WR
* The transmission is not allowed for this stream (e.g., a frame
2013-07-25 14:18:13 +02:00
* with END_STREAM flag set has already sent)
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_INVALID_STREAM_ID
* The stream ID is invalid.
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_STREAM_CLOSING
* RST_STREAM was queued for this stream.
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_INVALID_STREAM_STATE
2013-07-25 14:18:13 +02:00
* The state of the stream is not valid.
*/
2013-07-25 14:07:38 +02:00
static int nghttp2_session_predicate_response_headers_send
(nghttp2_session *session, int32_t stream_id)
{
2013-07-12 17:19:03 +02:00
nghttp2_stream *stream = nghttp2_session_get_stream(session, stream_id);
int r;
2013-07-12 17:19:03 +02:00
r = nghttp2_predicate_stream_for_send(stream);
if(r != 0) {
return r;
}
2013-07-12 17:19:03 +02:00
if(nghttp2_session_is_my_stream_id(session, stream_id)) {
return NGHTTP2_ERR_INVALID_STREAM_ID;
} else {
2013-07-12 17:19:03 +02:00
if(stream->state == NGHTTP2_STREAM_OPENING) {
return 0;
2013-07-12 17:19:03 +02:00
} else if(stream->state == NGHTTP2_STREAM_CLOSING) {
return NGHTTP2_ERR_STREAM_CLOSING;
} else {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_INVALID_STREAM_STATE;
}
}
}
2013-07-24 18:49:05 +02:00
/*
* This function checks HEADERS for reserved stream can be sent. The
* stream |stream_id| must be reserved state and the |session| is
* server side.
*
* 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 session is client-side and/or stream is not reserved state
* NGHTTP2_ERR_STREAM_CLOSED
* RST_STREAM was queued for this stream.
*/
2013-07-25 14:07:38 +02:00
static int nghttp2_session_predicate_push_response_headers_send
(nghttp2_session *session, int32_t stream_id)
2013-07-24 18:49:05 +02:00
{
nghttp2_stream *stream = nghttp2_session_get_stream(session, stream_id);
int r;
/* TODO Should disallow HEADERS if GOAWAY has already been issued? */
r = nghttp2_predicate_stream_for_send(stream);
if(r != 0) {
return r;
}
if(!session->server || stream->state != NGHTTP2_STREAM_RESERVED) {
/* Only server can send HEADERS for reserved streams */
return NGHTTP2_ERR_PROTO;
}
if(stream->state == NGHTTP2_STREAM_CLOSING) {
return NGHTTP2_ERR_STREAM_CLOSING;
}
return 0;
}
2012-02-23 16:02:29 +01:00
/*
2013-07-23 14:33:28 +02:00
* This function checks frames belongs to the stream |stream_id| can
* be sent.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_STREAM_CLOSED
* The stream is already closed or does not exist.
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_STREAM_SHUT_WR
* The transmission is not allowed for this stream (e.g., a frame
2013-07-25 14:18:13 +02:00
* with END_STREAM flag set has already sent)
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_STREAM_CLOSING
* RST_STREAM was queued for this stream.
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_INVALID_STREAM_STATE
2013-07-25 14:18:13 +02:00
* The state of the stream is not valid.
2012-02-23 16:02:29 +01:00
*/
2013-07-23 14:33:28 +02:00
static int nghttp2_session_predicate_stream_frame_send
(nghttp2_session* session, int32_t stream_id)
2012-02-23 16:02:29 +01:00
{
2013-07-12 17:19:03 +02:00
nghttp2_stream *stream = nghttp2_session_get_stream(session, stream_id);
int r;
2013-07-12 17:19:03 +02:00
r = nghttp2_predicate_stream_for_send(stream);
if(r != 0) {
return r;
2012-02-23 16:02:29 +01:00
}
2013-07-12 17:19:03 +02:00
if(nghttp2_session_is_my_stream_id(session, stream_id)) {
if(stream->state != NGHTTP2_STREAM_CLOSING) {
return 0;
} else {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_STREAM_CLOSING;
}
2012-02-23 16:02:29 +01:00
} else {
2013-07-12 17:19:03 +02:00
if(stream->state == NGHTTP2_STREAM_OPENED) {
return 0;
2013-07-12 17:19:03 +02:00
} else if(stream->state == NGHTTP2_STREAM_CLOSING) {
return NGHTTP2_ERR_STREAM_CLOSING;
} else {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_INVALID_STREAM_STATE;
}
2012-02-23 16:02:29 +01:00
}
}
2013-07-23 14:33:28 +02:00
/*
* This function checks HEADERS, which is neither stream-opening nor
* first response header, with the stream ID |stream_id| can be sent
* at this time.
*/
static int nghttp2_session_predicate_headers_send(nghttp2_session *session,
int32_t stream_id)
{
return nghttp2_session_predicate_stream_frame_send(session, stream_id);
}
/*
* This function checks PRIORITY frame with stream ID |stream_id| can
* be sent at this time.
*/
static int nghttp2_session_predicate_priority_send
(nghttp2_session *session, int32_t stream_id)
{
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, stream_id);
if(stream == NULL) {
return NGHTTP2_ERR_STREAM_CLOSED;
}
/* Sending PRIORITY to reserved state is OK */
if(stream->state != NGHTTP2_STREAM_CLOSING) {
return 0;
} else {
return NGHTTP2_ERR_STREAM_CLOSING;
}
}
2013-07-24 18:49:05 +02:00
/*
* This function checks PUSH_PROMISE frame |frame| with stream ID
* |stream_id| 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 GOAWAY is already sent or
* received.
* NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE
* Stream ID has reached the maximum value. Therefore no stream ID
* is available.
* 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)
*/
static int nghttp2_session_predicate_push_promise_send
(nghttp2_session *session, int32_t stream_id)
{
int rv;
nghttp2_stream *stream;
if(!session->server || nghttp2_session_is_my_stream_id(session, stream_id)) {
/* Only server is allowed to push. And associated stream must be
created from client side */
return NGHTTP2_ERR_PROTO;
}
stream = nghttp2_session_get_stream(session, stream_id);
rv = nghttp2_predicate_stream_for_send(stream);
if(rv != 0) {
return rv;
}
if(stream->state == NGHTTP2_STREAM_CLOSING) {
return NGHTTP2_ERR_STREAM_CLOSING;
}
if(session->goaway_flags) {
/* When GOAWAY is sent or received, peer must not promise new
stream ID */
return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
}
/* All 32bit signed stream IDs are spent. */
if(session->next_stream_id > INT32_MAX) {
return NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE;
}
return 0;
}
2012-02-25 16:12:32 +01:00
/*
* This function checks WINDOW_UPDATE with the stream ID |stream_id|
2013-07-25 14:18:13 +02:00
* 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:
*
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_STREAM_CLOSED
* The stream is already closed or does not exist.
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_STREAM_CLOSING
* RST_STREAM was queued for this stream.
2012-02-25 16:12:32 +01:00
*/
2013-07-12 17:19:03 +02:00
static int nghttp2_session_predicate_window_update_send
2013-07-15 14:45:59 +02:00
(nghttp2_session *session, int32_t stream_id)
2012-02-25 16:12:32 +01:00
{
2013-07-15 14:45:59 +02:00
nghttp2_stream *stream;
if(stream_id == 0) {
/* Connection-level window update */
return 0;
}
stream = nghttp2_session_get_stream(session, stream_id);
2012-02-25 16:12:32 +01:00
if(stream == NULL) {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_STREAM_CLOSED;
}
2013-07-24 18:49:05 +02:00
if(stream->state != NGHTTP2_STREAM_CLOSING &&
stream->state != NGHTTP2_STREAM_RESERVED) {
2012-02-25 16:12:32 +01:00
return 0;
} else {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_STREAM_CLOSING;
2012-02-25 16:12:32 +01:00
}
}
/*
2013-07-16 13:54:24 +02:00
* 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.
2012-02-25 16:12:32 +01:00
*/
2013-07-12 17:19:03 +02:00
static size_t nghttp2_session_next_data_read(nghttp2_session *session,
nghttp2_stream *stream)
2012-02-25 16:12:32 +01:00
{
2013-07-15 14:45:59 +02:00
/* TODO implement connection-level flow control here */
2013-07-16 13:54:24 +02:00
if(session->remote_flow_control == 0 && stream->remote_flow_control == 0) {
2013-07-12 17:19:03 +02:00
return NGHTTP2_DATA_PAYLOAD_LENGTH;
2012-02-25 16:12:32 +01:00
} else {
2013-07-16 13:54:24 +02:00
int32_t session_window_size =
session->remote_flow_control ? session->remote_window_size : INT32_MAX;
2013-07-16 13:54:24 +02:00
int32_t stream_window_size =
stream->remote_flow_control ? stream->remote_window_size : INT32_MAX;
2013-07-16 13:54:24 +02:00
int32_t window_size = nghttp2_min(session_window_size,
stream_window_size);
if(window_size > 0) {
return window_size < NGHTTP2_DATA_PAYLOAD_LENGTH ?
window_size : NGHTTP2_DATA_PAYLOAD_LENGTH;
} else {
return 0;
}
2012-02-25 16:12:32 +01:00
}
}
/*
* This function checks DATA with the stream ID |stream_id| can be
* sent at this time.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_STREAM_CLOSED
* The stream is already closed or does not exist.
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_STREAM_SHUT_WR
* The transmission is not allowed for this stream (e.g., a frame
2013-07-25 14:18:13 +02:00
* with END_STREAM flag set has already sent)
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_DEFERRED_DATA_EXIST
* Another DATA frame has already been deferred.
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_STREAM_CLOSING
* RST_STREAM was queued for this stream.
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_INVALID_STREAM_STATE
2013-07-25 14:18:13 +02:00
* The state of the stream is not valid.
*/
2013-07-12 17:19:03 +02:00
static int nghttp2_session_predicate_data_send(nghttp2_session *session,
int32_t stream_id)
{
2013-07-12 17:19:03 +02:00
nghttp2_stream *stream = nghttp2_session_get_stream(session, stream_id);
int r;
2013-07-12 17:19:03 +02:00
r = nghttp2_predicate_stream_for_send(stream);
if(r != 0) {
return r;
}
if(stream->deferred_data != NULL) {
/* stream->deferred_data != NULL means previously queued DATA
frame has not been sent. We don't allow new DATA frame is sent
in this case. */
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_DEFERRED_DATA_EXIST;
}
2013-07-12 17:19:03 +02:00
if(nghttp2_session_is_my_stream_id(session, stream_id)) {
/* If stream->state is NGHTTP2_STREAM_CLOSING, RST_STREAM was
queued but not yet sent. In this case, we won't send DATA
frames. This is because in the current architecture, DATA and
RST_STREAM in the same stream have same priority and DATA is
small seq number. So RST_STREAM will not be sent until all DATA
frames are sent. This is not desirable situation; we want to
close stream as soon as possible. To achieve this, we remove
DATA frame before RST_STREAM. */
2013-07-24 18:49:05 +02:00
if(stream->state != NGHTTP2_STREAM_CLOSING &&
stream->state != NGHTTP2_STREAM_RESERVED) {
return 0;
} else {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_STREAM_CLOSING;
}
} else {
2013-07-12 17:19:03 +02:00
if(stream->state == NGHTTP2_STREAM_OPENED) {
return 0;
2013-07-12 17:19:03 +02:00
} else if(stream->state == NGHTTP2_STREAM_CLOSING) {
return NGHTTP2_ERR_STREAM_CLOSING;
} else {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_INVALID_STREAM_STATE;
}
}
}
2013-07-12 17:19:03 +02:00
static ssize_t nghttp2_session_prep_frame(nghttp2_session *session,
nghttp2_outbound_item *item)
{
ssize_t framebuflen = 0;
2013-07-15 14:45:59 +02:00
if(item->frame_cat == NGHTTP2_CAT_CTRL) {
2013-07-12 17:19:03 +02:00
nghttp2_frame *frame;
frame = nghttp2_outbound_item_get_ctrl_frame(item);
2013-07-15 14:45:59 +02:00
switch(frame->hd.type) {
2013-07-24 18:49:05 +02:00
case NGHTTP2_HEADERS: {
2013-07-15 14:45:59 +02:00
if(frame->hd.stream_id == -1) {
/* initial HEADERS, which opens stream */
int r;
frame->headers.cat = NGHTTP2_HCAT_REQUEST;
2013-07-25 14:07:38 +02:00
r = nghttp2_session_predicate_request_headers_send(session,
&frame->headers);
2013-07-15 14:45:59 +02:00
if(r != 0) {
return r;
2012-04-05 18:45:39 +02:00
}
2013-07-19 17:08:14 +02:00
frame->hd.stream_id = session->next_stream_id;
2013-07-15 14:45:59 +02:00
session->next_stream_id += 2;
2013-07-25 14:07:38 +02:00
} else if(nghttp2_session_predicate_push_response_headers_send
2013-07-24 18:49:05 +02:00
(session, frame->hd.stream_id) == 0) {
frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE;
2013-07-25 14:07:38 +02:00
} else if(nghttp2_session_predicate_response_headers_send
2013-07-15 14:45:59 +02:00
(session, frame->hd.stream_id) == 0) {
frame->headers.cat = NGHTTP2_HCAT_RESPONSE;
2013-07-15 14:45:59 +02:00
} else {
int r;
frame->headers.cat = NGHTTP2_HCAT_HEADERS;
r = nghttp2_session_predicate_headers_send(session,
frame->hd.stream_id);
if(r != 0) {
return r;
}
2013-07-19 17:08:14 +02:00
}
framebuflen = nghttp2_frame_pack_headers(&session->aob.framebuf,
&session->aob.framebufmax,
&frame->headers,
&session->hd_deflater);
nghttp2_hd_end_headers(&session->hd_deflater);
if(framebuflen < 0) {
return framebuflen;
}
switch(frame->headers.cat) {
case NGHTTP2_HCAT_REQUEST: {
2013-07-19 17:08:14 +02:00
nghttp2_headers_aux_data *aux_data;
aux_data = (nghttp2_headers_aux_data*)item->aux_data;
if(nghttp2_session_open_stream
(session, frame->hd.stream_id,
frame->hd.flags,
frame->headers.pri,
NGHTTP2_STREAM_INITIAL,
aux_data ? aux_data->stream_user_data : NULL) == NULL) {
return NGHTTP2_ERR_NOMEM;
2013-07-15 14:45:59 +02:00
}
break;
}
case NGHTTP2_HCAT_PUSH_RESPONSE: {
2013-07-24 18:49:05 +02:00
nghttp2_headers_aux_data *aux_data;
aux_data = (nghttp2_headers_aux_data*)item->aux_data;
if(aux_data) {
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
stream->stream_user_data = aux_data->stream_user_data;
}
break;
}
default:
break;
}
break;
2013-07-24 18:49:05 +02:00
}
case NGHTTP2_PRIORITY: {
int r;
r = nghttp2_session_predicate_priority_send
(session, frame->hd.stream_id);
if(r != 0) {
return r;
}
framebuflen = nghttp2_frame_pack_priority(&session->aob.framebuf,
&session->aob.framebufmax,
&frame->priority);
if(framebuflen < 0) {
return framebuflen;
}
break;
}
2013-07-12 17:19:03 +02:00
case NGHTTP2_RST_STREAM:
framebuflen = nghttp2_frame_pack_rst_stream(&session->aob.framebuf,
&session->aob.framebufmax,
&frame->rst_stream);
if(framebuflen < 0) {
return framebuflen;
}
break;
2013-07-12 17:19:03 +02:00
case NGHTTP2_SETTINGS:
framebuflen = nghttp2_frame_pack_settings(&session->aob.framebuf,
&session->aob.framebufmax,
&frame->settings);
if(framebuflen < 0) {
return framebuflen;
}
break;
2013-07-24 18:49:05 +02:00
case NGHTTP2_PUSH_PROMISE: {
int r;
nghttp2_stream *stream;
r = nghttp2_session_predicate_push_promise_send(session,
frame->hd.stream_id);
if(r != 0) {
return r;
}
frame->push_promise.promised_stream_id = session->next_stream_id;
session->next_stream_id += 2;
framebuflen = nghttp2_frame_pack_push_promise(&session->aob.framebuf,
&session->aob.framebufmax,
&frame->push_promise,
&session->hd_deflater);
nghttp2_hd_end_headers(&session->hd_deflater);
if(framebuflen < 0) {
return framebuflen;
}
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
assert(stream);
if(nghttp2_session_open_stream
(session, frame->push_promise.promised_stream_id,
frame->hd.flags,
nghttp2_pushed_stream_pri(stream),
NGHTTP2_STREAM_RESERVED,
NULL) == NULL) {
return NGHTTP2_ERR_NOMEM;
}
break;
}
2013-07-12 17:19:03 +02:00
case NGHTTP2_PING:
framebuflen = nghttp2_frame_pack_ping(&session->aob.framebuf,
&session->aob.framebufmax,
&frame->ping);
if(framebuflen < 0) {
return framebuflen;
}
break;
2013-07-12 17:19:03 +02:00
case NGHTTP2_WINDOW_UPDATE: {
int r;
2013-07-12 17:19:03 +02:00
r = nghttp2_session_predicate_window_update_send
2013-07-15 14:45:59 +02:00
(session, frame->hd.stream_id);
if(r != 0) {
return r;
}
2013-07-12 17:19:03 +02:00
framebuflen = nghttp2_frame_pack_window_update(&session->aob.framebuf,
&session->aob.framebufmax,
&frame->window_update);
if(framebuflen < 0) {
return framebuflen;
}
break;
}
2013-07-12 17:19:03 +02:00
case NGHTTP2_GOAWAY:
if(session->goaway_flags & NGHTTP2_GOAWAY_SEND) {
/* TODO The spec does not mandate that both endpoints have to
2013-07-15 14:45:59 +02:00
exchange GOAWAY. This implementation allows receiver of
first GOAWAY can sent its own GOAWAY to tell the remote
peer that last-stream-id. */
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_GOAWAY_ALREADY_SENT;
}
2013-07-12 17:19:03 +02:00
framebuflen = nghttp2_frame_pack_goaway(&session->aob.framebuf,
&session->aob.framebufmax,
&frame->goaway);
if(framebuflen < 0) {
return framebuflen;
}
break;
default:
2013-07-12 17:19:03 +02:00
framebuflen = NGHTTP2_ERR_INVALID_ARGUMENT;
2012-01-28 11:22:38 +01:00
}
2013-07-15 14:45:59 +02:00
} else if(item->frame_cat == NGHTTP2_CAT_DATA) {
size_t next_readmax;
2013-07-12 17:19:03 +02:00
nghttp2_stream *stream;
nghttp2_data *data_frame;
int r;
2013-07-12 17:19:03 +02:00
data_frame = nghttp2_outbound_item_get_data_frame(item);
2013-07-15 14:45:59 +02:00
r = nghttp2_session_predicate_data_send(session, data_frame->hd.stream_id);
if(r != 0) {
return r;
}
2013-07-15 14:45:59 +02:00
stream = nghttp2_session_get_stream(session, data_frame->hd.stream_id);
2012-02-25 16:12:32 +01:00
/* Assuming stream is not NULL */
assert(stream);
2013-07-12 17:19:03 +02:00
next_readmax = nghttp2_session_next_data_read(session, stream);
if(next_readmax == 0) {
2013-07-12 17:19:03 +02:00
nghttp2_stream_defer_data(stream, item, NGHTTP2_DEFERRED_FLOW_CONTROL);
return NGHTTP2_ERR_DEFERRED;
2012-02-25 16:12:32 +01:00
}
2013-07-12 17:19:03 +02:00
framebuflen = nghttp2_session_pack_data(session,
&session->aob.framebuf,
&session->aob.framebufmax,
next_readmax,
data_frame);
2013-07-12 17:19:03 +02:00
if(framebuflen == NGHTTP2_ERR_DEFERRED) {
nghttp2_stream_defer_data(stream, item, NGHTTP2_DEFERRED_NONE);
return NGHTTP2_ERR_DEFERRED;
} else if(framebuflen == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
2013-07-15 14:45:59 +02:00
r = nghttp2_session_add_rst_stream(session, data_frame->hd.stream_id,
2013-07-12 17:19:03 +02:00
NGHTTP2_INTERNAL_ERROR);
if(r == 0) {
return framebuflen;
} else {
return r;
}
} else if(framebuflen < 0) {
return framebuflen;
}
} else {
/* Unreachable */
assert(0);
}
return framebuflen;
}
2013-07-12 17:19:03 +02:00
nghttp2_outbound_item* nghttp2_session_get_ob_pq_top
(nghttp2_session *session)
{
2013-07-12 17:19:03 +02:00
return (nghttp2_outbound_item*)nghttp2_pq_top(&session->ob_pq);
}
2013-07-12 17:19:03 +02:00
nghttp2_outbound_item* nghttp2_session_get_next_ob_item
(nghttp2_session *session)
{
2013-07-12 17:19:03 +02:00
if(nghttp2_pq_empty(&session->ob_pq)) {
if(nghttp2_pq_empty(&session->ob_ss_pq)) {
return NULL;
} else {
/* Return item only when concurrent connection limit is not
reached */
2013-07-12 17:19:03 +02:00
if(nghttp2_session_is_outgoing_concurrent_streams_max(session)) {
return NULL;
} else {
2013-07-12 17:19:03 +02:00
return nghttp2_pq_top(&session->ob_ss_pq);
}
}
} else {
2013-07-12 17:19:03 +02:00
if(nghttp2_pq_empty(&session->ob_ss_pq)) {
return nghttp2_pq_top(&session->ob_pq);
} else {
2013-07-25 14:18:13 +02:00
nghttp2_outbound_item *item, *headers_item;
2013-07-12 17:19:03 +02:00
item = nghttp2_pq_top(&session->ob_pq);
2013-07-25 14:18:13 +02:00
headers_item = nghttp2_pq_top(&session->ob_ss_pq);
2013-07-12 17:19:03 +02:00
if(nghttp2_session_is_outgoing_concurrent_streams_max(session) ||
2013-07-25 14:18:13 +02:00
item->pri < headers_item->pri ||
(item->pri == headers_item->pri &&
item->seq < headers_item->seq)) {
return item;
} else {
2013-07-25 14:18:13 +02:00
return headers_item;
}
}
}
}
2013-07-12 17:19:03 +02:00
nghttp2_outbound_item* nghttp2_session_pop_next_ob_item
(nghttp2_session *session)
{
2013-07-12 17:19:03 +02:00
if(nghttp2_pq_empty(&session->ob_pq)) {
if(nghttp2_pq_empty(&session->ob_ss_pq)) {
return NULL;
} else {
/* Pop item only when concurrent connection limit is not
reached */
2013-07-12 17:19:03 +02:00
if(nghttp2_session_is_outgoing_concurrent_streams_max(session)) {
return NULL;
} else {
2013-07-12 17:19:03 +02:00
nghttp2_outbound_item *item;
item = nghttp2_pq_top(&session->ob_ss_pq);
nghttp2_pq_pop(&session->ob_ss_pq);
return item;
}
}
} else {
2013-07-12 17:19:03 +02:00
if(nghttp2_pq_empty(&session->ob_ss_pq)) {
nghttp2_outbound_item *item;
item = nghttp2_pq_top(&session->ob_pq);
nghttp2_pq_pop(&session->ob_pq);
return item;
} else {
2013-07-25 14:18:13 +02:00
nghttp2_outbound_item *item, *headers_item;
2013-07-12 17:19:03 +02:00
item = nghttp2_pq_top(&session->ob_pq);
2013-07-25 14:18:13 +02:00
headers_item = nghttp2_pq_top(&session->ob_ss_pq);
2013-07-12 17:19:03 +02:00
if(nghttp2_session_is_outgoing_concurrent_streams_max(session) ||
2013-07-25 14:18:13 +02:00
item->pri < headers_item->pri ||
(item->pri == headers_item->pri &&
item->seq < headers_item->seq)) {
2013-07-12 17:19:03 +02:00
nghttp2_pq_pop(&session->ob_pq);
return item;
} else {
2013-07-12 17:19:03 +02:00
nghttp2_pq_pop(&session->ob_ss_pq);
2013-07-25 14:18:13 +02:00
return headers_item;
}
}
}
}
/*
* Called after a frame is sent.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_NOMEM
* Out of memory.
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_CALLBACK_FAILURE
* The callback function failed.
*/
2013-07-12 17:19:03 +02:00
static int nghttp2_session_after_frame_sent(nghttp2_session *session)
{
2013-07-12 17:19:03 +02:00
nghttp2_outbound_item *item = session->aob.item;
2013-07-15 14:45:59 +02:00
if(item->frame_cat == NGHTTP2_CAT_CTRL) {
2013-07-12 17:19:03 +02:00
nghttp2_frame *frame;
frame = nghttp2_outbound_item_get_ctrl_frame(session->aob.item);
2013-07-15 14:45:59 +02:00
if(session->callbacks.on_frame_send_callback) {
session->callbacks.on_frame_send_callback(session, frame,
session->user_data);
}
2013-07-15 14:45:59 +02:00
switch(frame->hd.type) {
case NGHTTP2_HEADERS: {
2013-07-12 17:19:03 +02:00
nghttp2_stream *stream =
2013-07-15 14:45:59 +02:00
nghttp2_session_get_stream(session, frame->hd.stream_id);
nghttp2_headers_aux_data *aux_data;
if(stream) {
2013-07-15 14:45:59 +02:00
switch(frame->headers.cat) {
case NGHTTP2_HCAT_REQUEST: {
2013-07-15 14:45:59 +02:00
stream->state = NGHTTP2_STREAM_OPENING;
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
}
2013-07-15 14:45:59 +02:00
nghttp2_session_close_stream_if_shut_rdwr(session, stream);
/* We assume aux_data is a pointer to nghttp2_headers_aux_data */
aux_data = (nghttp2_headers_aux_data*)item->aux_data;
if(aux_data && aux_data->data_prd) {
int r;
/* nghttp2_submit_data() makes a copy of aux_data->data_prd */
r = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM,
frame->hd.stream_id, aux_data->data_prd);
if(r != 0) {
/* FATAL error */
assert(r < NGHTTP2_ERR_FATAL);
/* TODO If r is not FATAL, we should send RST_STREAM. */
return r;
}
}
break;
}
case NGHTTP2_HCAT_RESPONSE:
case NGHTTP2_HCAT_PUSH_RESPONSE:
2013-07-15 14:45:59 +02:00
stream->state = NGHTTP2_STREAM_OPENED;
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
}
2013-07-15 14:45:59 +02:00
nghttp2_session_close_stream_if_shut_rdwr(session, stream);
/* We assume aux_data is a pointer to nghttp2_headers_aux_data */
aux_data = (nghttp2_headers_aux_data*)item->aux_data;
if(aux_data && aux_data->data_prd) {
int r;
r = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM,
frame->hd.stream_id, aux_data->data_prd);
if(r != 0) {
/* FATAL error */
assert(r < NGHTTP2_ERR_FATAL);
/* TODO If r is not FATAL, we should send RST_STREAM. */
return r;
}
}
break;
case NGHTTP2_HCAT_HEADERS:
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
}
nghttp2_session_close_stream_if_shut_rdwr(session, stream);
break;
}
}
break;
}
2013-07-23 14:33:28 +02:00
case NGHTTP2_PRIORITY:
/* nothing to do */
break;
2013-07-12 17:19:03 +02:00
case NGHTTP2_RST_STREAM:
2013-07-15 14:45:59 +02:00
nghttp2_session_close_stream(session, frame->hd.stream_id,
frame->rst_stream.error_code);
break;
2013-07-12 17:19:03 +02:00
case NGHTTP2_SETTINGS:
/* nothing to do */
break;
2013-07-24 18:49:05 +02:00
case NGHTTP2_PUSH_PROMISE:
/* nothing to do */
break;
2013-07-12 17:19:03 +02:00
case NGHTTP2_PING:
2013-07-15 14:45:59 +02:00
/* nothing to do */
break;
2013-07-12 17:19:03 +02:00
case NGHTTP2_GOAWAY:
session->goaway_flags |= NGHTTP2_GOAWAY_SEND;
break;
2013-07-12 17:19:03 +02:00
case NGHTTP2_WINDOW_UPDATE:
/* nothing to do */
break;
2012-02-23 16:02:29 +01:00
}
2013-07-12 17:19:03 +02:00
nghttp2_active_outbound_item_reset(&session->aob);
2013-07-15 14:45:59 +02:00
} else if(item->frame_cat == NGHTTP2_CAT_DATA) {
int r;
2013-07-12 17:19:03 +02:00
nghttp2_data *data_frame;
data_frame = nghttp2_outbound_item_get_data_frame(session->aob.item);
if(session->callbacks.on_data_send_callback) {
session->callbacks.on_data_send_callback
(session,
2013-07-15 17:15:04 +02:00
session->aob.framebuflen - NGHTTP2_FRAME_HEAD_LENGTH,
2013-07-15 14:45:59 +02:00
data_frame->eof ? data_frame->hd.flags :
(data_frame->hd.flags & (~NGHTTP2_FLAG_END_STREAM)),
data_frame->hd.stream_id,
session->user_data);
}
2013-07-15 14:45:59 +02:00
if(data_frame->eof && (data_frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
2013-07-12 17:19:03 +02:00
nghttp2_stream *stream =
2013-07-15 14:45:59 +02:00
nghttp2_session_get_stream(session, data_frame->hd.stream_id);
if(stream) {
2013-07-12 17:19:03 +02:00
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
nghttp2_session_close_stream_if_shut_rdwr(session, stream);
}
}
/* If session is closed or RST_STREAM was queued, we won't send
further data. */
if(data_frame->eof ||
2013-07-12 17:19:03 +02:00
nghttp2_session_predicate_data_send(session,
2013-07-15 14:45:59 +02:00
data_frame->hd.stream_id) != 0) {
2013-07-12 17:19:03 +02:00
nghttp2_active_outbound_item_reset(&session->aob);
} else {
2013-07-12 17:19:03 +02:00
nghttp2_outbound_item* next_item;
next_item = nghttp2_session_get_next_ob_item(session);
2012-02-25 16:12:32 +01:00
/* If priority of this stream is higher or equal to other stream
waiting at the top of the queue, we continue to send this
data. */
if(next_item == NULL || session->aob.item->pri <= next_item->pri) {
size_t next_readmax;
2013-07-12 17:19:03 +02:00
nghttp2_stream *stream;
2013-07-15 14:45:59 +02:00
stream = nghttp2_session_get_stream(session, data_frame->hd.stream_id);
2012-02-25 16:12:32 +01:00
/* Assuming stream is not NULL */
assert(stream);
2013-07-12 17:19:03 +02:00
next_readmax = nghttp2_session_next_data_read(session, stream);
if(next_readmax == 0) {
2013-07-12 17:19:03 +02:00
nghttp2_stream_defer_data(stream, session->aob.item,
NGHTTP2_DEFERRED_FLOW_CONTROL);
2012-02-25 16:12:32 +01:00
session->aob.item = NULL;
2013-07-12 17:19:03 +02:00
nghttp2_active_outbound_item_reset(&session->aob);
2012-02-25 16:12:32 +01:00
return 0;
}
2013-07-12 17:19:03 +02:00
r = nghttp2_session_pack_data(session,
&session->aob.framebuf,
&session->aob.framebufmax,
next_readmax,
data_frame);
2013-07-12 17:19:03 +02:00
if(r == NGHTTP2_ERR_DEFERRED) {
nghttp2_stream_defer_data(stream, session->aob.item,
NGHTTP2_DEFERRED_NONE);
session->aob.item = NULL;
2013-07-12 17:19:03 +02:00
nghttp2_active_outbound_item_reset(&session->aob);
} else if(r == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
/* Stop DATA frame chain and issue RST_STREAM to close the
stream. We don't return
2013-07-12 17:19:03 +02:00
NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE intentionally. */
2013-07-15 14:45:59 +02:00
r = nghttp2_session_add_rst_stream(session, data_frame->hd.stream_id,
2013-07-12 17:19:03 +02:00
NGHTTP2_INTERNAL_ERROR);
nghttp2_active_outbound_item_reset(&session->aob);
if(r != 0) {
return r;
}
} else if(r < 0) {
2013-07-12 17:19:03 +02:00
/* In this context, r is either NGHTTP2_ERR_NOMEM or
NGHTTP2_ERR_CALLBACK_FAILURE */
nghttp2_active_outbound_item_reset(&session->aob);
return r;
} else {
session->aob.framebuflen = r;
session->aob.framebufoff = 0;
}
} else {
2013-07-12 17:19:03 +02:00
r = nghttp2_pq_push(&session->ob_pq, session->aob.item);
if(r == 0) {
session->aob.item = NULL;
2013-07-12 17:19:03 +02:00
nghttp2_active_outbound_item_reset(&session->aob);
} else {
/* FATAL error */
2013-07-12 17:19:03 +02:00
assert(r < NGHTTP2_ERR_FATAL);
nghttp2_active_outbound_item_reset(&session->aob);
return r;
}
}
}
} else {
/* Unreachable */
assert(0);
}
return 0;
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_send(nghttp2_session *session)
{
int r;
while(1) {
const uint8_t *data;
size_t datalen;
ssize_t sentlen;
if(session->aob.item == NULL) {
2013-07-12 17:19:03 +02:00
nghttp2_outbound_item *item;
ssize_t framebuflen;
2013-07-12 17:19:03 +02:00
item = nghttp2_session_pop_next_ob_item(session);
if(item == NULL) {
break;
}
2013-07-12 17:19:03 +02:00
framebuflen = nghttp2_session_prep_frame(session, item);
if(framebuflen == NGHTTP2_ERR_DEFERRED) {
continue;
} else if(framebuflen < 0) {
2013-07-19 17:21:07 +02:00
/* TODO If the error comes from compressor, the connection
must be closed. */
2013-07-15 14:45:59 +02:00
if(item->frame_cat == NGHTTP2_CAT_CTRL &&
session->callbacks.on_frame_not_send_callback &&
2013-07-12 17:19:03 +02:00
nghttp2_is_non_fatal(framebuflen)) {
/* The library is responsible for the transmission of
WINDOW_UPDATE frame, so we don't call error callback for
it. */
2013-07-15 14:45:59 +02:00
nghttp2_frame *frame = nghttp2_outbound_item_get_ctrl_frame(item);
if(frame->hd.type != NGHTTP2_WINDOW_UPDATE) {
session->callbacks.on_frame_not_send_callback
(session, frame, framebuflen, session->user_data);
}
}
2013-07-12 17:19:03 +02:00
nghttp2_outbound_item_free(item);
free(item);
2013-07-15 14:45:59 +02:00
if(framebuflen == NGHTTP2_ERR_HEADER_COMP) {
// If header compression error occurred, should terminiate
// connection.
framebuflen = nghttp2_session_fail_session(session,
NGHTTP2_INTERNAL_ERROR);
}
2013-07-12 17:19:03 +02:00
if(nghttp2_is_fatal(framebuflen)) {
2012-01-28 11:22:38 +01:00
return framebuflen;
} else {
continue;
2012-01-28 11:22:38 +01:00
}
}
session->aob.item = item;
session->aob.framebuflen = framebuflen;
/* Call before_send callback */
2013-07-15 14:45:59 +02:00
if(item->frame_cat == NGHTTP2_CAT_CTRL &&
session->callbacks.before_frame_send_callback) {
session->callbacks.before_frame_send_callback
(session,
2013-07-12 17:19:03 +02:00
nghttp2_outbound_item_get_ctrl_frame(item),
session->user_data);
}
}
data = session->aob.framebuf + session->aob.framebufoff;
datalen = session->aob.framebuflen - session->aob.framebufoff;
sentlen = session->callbacks.send_callback(session, data, datalen, 0,
session->user_data);
if(sentlen < 0) {
2013-07-12 17:19:03 +02:00
if(sentlen == NGHTTP2_ERR_WOULDBLOCK) {
return 0;
} else {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
} else {
session->aob.framebufoff += sentlen;
2013-07-15 14:45:59 +02:00
if(session->aob.item->frame_cat == NGHTTP2_CAT_DATA) {
2013-07-12 17:19:03 +02:00
nghttp2_data *frame;
nghttp2_stream *stream;
2013-07-16 13:54:24 +02:00
uint16_t len = nghttp2_get_uint16(&session->aob.framebuf[0]);
2013-07-12 17:19:03 +02:00
frame = nghttp2_outbound_item_get_data_frame(session->aob.item);
2013-07-15 14:45:59 +02:00
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if(stream && stream->remote_flow_control) {
stream->remote_window_size -= len;
2013-07-16 13:54:24 +02:00
}
if(session->remote_flow_control) {
session->remote_window_size -= len;
2012-02-25 16:12:32 +01:00
}
}
if(session->aob.framebufoff == session->aob.framebuflen) {
/* Frame has completely sent */
2013-07-12 17:19:03 +02:00
r = nghttp2_session_after_frame_sent(session);
if(r < 0) {
/* FATAL */
2013-07-12 17:19:03 +02:00
assert(r < NGHTTP2_ERR_FATAL);
return r;
}
}
}
}
return 0;
}
2013-07-12 17:19:03 +02:00
static ssize_t nghttp2_recv(nghttp2_session *session, uint8_t *buf, size_t len)
{
ssize_t r;
r = session->callbacks.recv_callback
(session, buf, len, 0, session->user_data);
if(r > 0) {
if((size_t)r > len) {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
} else if(r < 0) {
2013-07-12 17:19:03 +02:00
if(r != NGHTTP2_ERR_WOULDBLOCK && r != NGHTTP2_ERR_EOF) {
r = NGHTTP2_ERR_CALLBACK_FAILURE;
}
}
return r;
}
2013-07-12 17:19:03 +02:00
static void nghttp2_session_call_on_request_recv
(nghttp2_session *session, int32_t stream_id)
{
if(session->callbacks.on_request_recv_callback) {
session->callbacks.on_request_recv_callback(session, stream_id,
session->user_data);
}
}
2013-07-15 14:45:59 +02:00
static void nghttp2_session_call_on_frame_received
(nghttp2_session *session, nghttp2_frame *frame)
{
2013-07-15 14:45:59 +02:00
if(session->callbacks.on_frame_recv_callback) {
session->callbacks.on_frame_recv_callback(session, frame,
session->user_data);
}
}
/*
* Checks whether received stream_id is valid.
* This function returns 1 if it succeeds, or 0.
*/
2013-07-12 17:19:03 +02:00
static int nghttp2_session_is_new_peer_stream_id(nghttp2_session *session,
int32_t stream_id)
{
if(stream_id == 0) {
return 0;
}
if(session->server) {
return stream_id % 2 == 1 && session->last_recv_stream_id < stream_id;
} else {
return stream_id % 2 == 0 && session->last_recv_stream_id < stream_id;
}
}
/*
2013-07-25 14:18:13 +02:00
* Validates received HEADERS frame |frame| with NGHTTP2_HCAT_REQUEST
* or NGHTTP2_HCAT_PUSH_RESPONSE category, which both opens new
* stream.
*
* This function returns 0 if it succeeds, or non-zero
* nghttp2_error_code.
*/
2013-07-25 14:18:13 +02:00
static int nghttp2_session_validate_request_headers(nghttp2_session *session,
nghttp2_headers *frame)
{
2013-07-12 17:19:03 +02:00
if(nghttp2_session_is_incoming_concurrent_streams_max(session)) {
2013-07-15 14:45:59 +02:00
/* The spec does not clearly say what to do when max concurrent
streams number is reached. The mod_spdy sends
2013-07-12 17:19:03 +02:00
NGHTTP2_REFUSED_STREAM and we think it is reasonable. So we
follow it. */
2013-07-12 17:19:03 +02:00
return NGHTTP2_REFUSED_STREAM;
}
return 0;
}
static int nghttp2_session_handle_parse_error(nghttp2_session *session,
nghttp2_frame_type type,
int lib_error_code,
nghttp2_error_code error_code)
{
if(session->callbacks.on_frame_recv_parse_error_callback) {
session->callbacks.on_frame_recv_parse_error_callback
(session,
type,
session->iframe.headbuf,
sizeof(session->iframe.headbuf),
session->iframe.buf,
session->iframe.buflen,
lib_error_code,
session->user_data);
}
return nghttp2_session_fail_session(session, error_code);
}
2013-07-12 17:19:03 +02:00
static int nghttp2_session_handle_invalid_stream
(nghttp2_session *session,
nghttp2_frame *frame,
2013-07-15 14:45:59 +02:00
nghttp2_error_code error_code)
{
int r;
r = nghttp2_session_add_rst_stream(session, frame->hd.stream_id, error_code);
if(r != 0) {
return r;
}
2013-07-15 14:45:59 +02:00
if(session->callbacks.on_invalid_frame_recv_callback) {
session->callbacks.on_invalid_frame_recv_callback
(session, frame, error_code, session->user_data);
}
return 0;
}
/*
* Handles invalid frame which causes connection error.
*/
static int nghttp2_session_handle_invalid_connection
(nghttp2_session *session,
nghttp2_frame *frame,
nghttp2_error_code error_code)
{
if(session->callbacks.on_invalid_frame_recv_callback) {
session->callbacks.on_invalid_frame_recv_callback
(session, frame, error_code, session->user_data);
}
return nghttp2_session_fail_session(session, error_code);
}
2013-07-25 14:07:38 +02:00
int nghttp2_session_on_request_headers_received(nghttp2_session *session,
nghttp2_frame *frame)
{
int r = 0;
2013-07-15 14:45:59 +02:00
nghttp2_error_code error_code = NGHTTP2_NO_ERROR;
if(frame->hd.stream_id == 0) {
return nghttp2_session_handle_invalid_connection(session, frame,
NGHTTP2_PROTOCOL_ERROR);
}
2012-01-28 11:22:38 +01:00
if(session->goaway_flags) {
2013-07-15 14:45:59 +02:00
/* We don't accept new stream after GOAWAY is sent or received. */
2012-01-28 11:22:38 +01:00
return 0;
}
if(!nghttp2_session_is_new_peer_stream_id(session, frame->hd.stream_id)) {
2013-07-15 14:45:59 +02:00
/* The spec says if an endpoint receives a HEADERS with invalid
stream ID, it MUST issue connection error with error code
PROTOCOL_ERROR */
return nghttp2_session_handle_invalid_connection
(session, frame, NGHTTP2_PROTOCOL_ERROR);
} else {
2013-07-15 14:45:59 +02:00
session->last_recv_stream_id = frame->hd.stream_id;
2013-07-25 14:18:13 +02:00
error_code = nghttp2_session_validate_request_headers
(session, &frame->headers);
2013-07-15 14:45:59 +02:00
}
if(error_code == 0) {
uint8_t flags = frame->hd.flags;
nghttp2_stream *stream;
stream = nghttp2_session_open_stream(session,
frame->hd.stream_id,
frame->hd.flags,
frame->headers.pri,
NGHTTP2_STREAM_OPENING,
NULL);
if(!stream) {
return NGHTTP2_ERR_NOMEM;
}
if(flags & NGHTTP2_FLAG_END_STREAM) {
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
}
nghttp2_session_call_on_frame_received(session, frame);
if(flags & NGHTTP2_FLAG_END_STREAM) {
nghttp2_session_call_on_request_recv(session, frame->hd.stream_id);
}
} else {
r = nghttp2_session_handle_invalid_stream(session, frame, error_code);
}
return r;
}
2013-07-25 14:07:38 +02:00
int nghttp2_session_on_response_headers_received(nghttp2_session *session,
nghttp2_frame *frame,
nghttp2_stream *stream)
{
2013-07-24 18:49:05 +02:00
/* 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 nghttp2_session_handle_invalid_connection(session, frame,
NGHTTP2_PROTOCOL_ERROR);
}
2013-07-15 14:45:59 +02:00
if((stream->shut_flags & NGHTTP2_SHUT_RD) == 0) {
2013-07-24 18:49:05 +02:00
stream->state = NGHTTP2_STREAM_OPENED;
nghttp2_session_call_on_frame_received(session, frame);
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
/* This is the last frame of this stream, so disallow
further receptions. */
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
nghttp2_session_close_stream_if_shut_rdwr(session, stream);
}
2013-07-15 14:45:59 +02:00
} else {
/* 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.
*/
2013-07-24 18:49:05 +02:00
return nghttp2_session_handle_invalid_stream(session, frame,
NGHTTP2_STREAM_CLOSED);
}
2013-07-24 18:49:05 +02:00
return 0;
}
2013-07-25 14:07:38 +02:00
int nghttp2_session_on_push_response_headers_received(nghttp2_session *session,
nghttp2_frame *frame,
nghttp2_stream *stream)
2013-07-24 18:49:05 +02:00
{
int rv = 0;
assert(!session->server && stream->state == NGHTTP2_STREAM_RESERVED);
if(frame->hd.stream_id == 0) {
return nghttp2_session_handle_invalid_connection(session, frame,
NGHTTP2_PROTOCOL_ERROR);
}
2013-07-24 18:49:05 +02:00
if(session->goaway_flags) {
/* We don't accept new stream after GOAWAY is sent or received. */
return 0;
}
2013-07-25 14:18:13 +02:00
rv = nghttp2_session_validate_request_headers(session, &frame->headers);
2013-07-24 18:49:05 +02:00
if(rv != 0) {
return nghttp2_session_handle_invalid_stream(session, frame, rv);
}
nghttp2_stream_promise_fulfilled(stream);
++session->num_incoming_streams;
nghttp2_session_call_on_frame_received(session, frame);
return 0;
}
2013-07-15 14:45:59 +02:00
int nghttp2_session_on_headers_received(nghttp2_session *session,
nghttp2_frame *frame,
nghttp2_stream *stream)
2012-01-27 15:22:27 +01:00
{
2013-07-15 14:45:59 +02:00
int r = 0;
int valid = 0;
nghttp2_error_code error_code = NGHTTP2_PROTOCOL_ERROR;
if(frame->hd.stream_id == 0) {
return nghttp2_session_handle_invalid_connection(session, frame,
NGHTTP2_PROTOCOL_ERROR);
}
2013-07-15 14:45:59 +02:00
if((stream->shut_flags & NGHTTP2_SHUT_RD) == 0) {
if(nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
if(stream->state == NGHTTP2_STREAM_OPENED) {
valid = 1;
nghttp2_session_call_on_frame_received(session, frame);
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
nghttp2_session_close_stream_if_shut_rdwr(session, stream);
}
} else if(stream->state == NGHTTP2_STREAM_CLOSING) {
/* This is race condition. NGHTTP2_STREAM_CLOSING indicates
that we queued RST_STREAM but it has not been sent. It will
eventually sent, so we just ignore this frame. */
valid = 1;
}
} else {
/* If this is remote peer initiated stream, it is OK unless it
2013-07-25 14:18:13 +02:00
has sent END_STREAM frame already. But if stream is in
2013-07-15 14:45:59 +02:00
NGHTTP2_STREAM_CLOSING, we discard the frame. This is a race
condition. */
valid = 1;
if(stream->state != NGHTTP2_STREAM_CLOSING) {
nghttp2_session_call_on_frame_received(session, frame);
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
nghttp2_session_call_on_request_recv(session, frame->hd.stream_id);
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
nghttp2_session_close_stream_if_shut_rdwr(session, stream);
}
}
}
} else {
/* 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.
*/
error_code = NGHTTP2_STREAM_CLOSED;
}
2013-07-15 14:45:59 +02:00
if(!valid) {
r = nghttp2_session_handle_invalid_stream(session, frame, error_code);
}
2013-07-15 14:45:59 +02:00
return r;
}
int nghttp2_session_on_priority_received(nghttp2_session *session,
nghttp2_frame *frame)
{
nghttp2_stream *stream;
if(frame->hd.stream_id == 0) {
return nghttp2_session_handle_invalid_connection(session, frame,
NGHTTP2_PROTOCOL_ERROR);
}
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if(stream) {
/* Only update priority on server side for now */
if(session->server) {
nghttp2_session_reprioritize_stream(session, stream,
frame->priority.pri);
}
nghttp2_session_call_on_frame_received(session, frame);
}
return 0;
}
2013-07-15 14:45:59 +02:00
int nghttp2_session_on_rst_stream_received(nghttp2_session *session,
nghttp2_frame *frame)
{
if(frame->hd.stream_id == 0) {
return nghttp2_session_handle_invalid_connection(session, frame,
NGHTTP2_PROTOCOL_ERROR);
}
2013-07-15 14:45:59 +02:00
nghttp2_session_call_on_frame_received(session, frame);
nghttp2_session_close_stream(session, frame->hd.stream_id,
frame->rst_stream.error_code);
return 0;
2012-01-27 15:22:27 +01:00
}
static int nghttp2_update_remote_initial_window_size_func
(nghttp2_map_entry *entry,
void *ptr)
{
int rv;
2013-07-12 17:19:03 +02:00
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. */
2013-07-16 13:54:24 +02:00
if(stream->deferred_data &&
(stream->deferred_flags & NGHTTP2_DEFERRED_FLOW_CONTROL) &&
stream->remote_window_size > 0 &&
2013-07-16 13:54:24 +02:00
(arg->session->remote_flow_control == 0 ||
arg->session->remote_window_size > 0)) {
2013-07-12 17:19:03 +02:00
rv = nghttp2_pq_push(&arg->session->ob_pq, stream->deferred_data);
if(rv == 0) {
2013-07-12 17:19:03 +02:00
nghttp2_stream_detach_deferred_data(stream);
} else {
/* FATAL */
2013-07-12 17:19:03 +02:00
assert(rv < NGHTTP2_ERR_FATAL);
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:
*
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_NOMEM
* Out of memory.
*/
static int nghttp2_session_update_remote_initial_window_size
2013-07-12 17:19:03 +02:00
(nghttp2_session *session,
int32_t new_initial_window_size)
{
2013-07-12 17:19:03 +02:00
nghttp2_update_window_size_arg arg;
arg.session = session;
arg.new_window_size = new_initial_window_size;
arg.old_window_size =
2013-07-12 17:19:03 +02:00
session->remote_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE];
return nghttp2_map_each(&session->streams,
nghttp2_update_remote_initial_window_size_func,
&arg);
}
static int nghttp2_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;
if(!stream->local_flow_control) {
return 0;
}
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_STREAM_WINDOW_UPDATE)) {
if(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 nghttp2_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,
nghttp2_update_local_initial_window_size_func,
&arg);
}
static int nghttp2_disable_remote_flow_control_func(nghttp2_map_entry *entry,
void *ptr)
{
nghttp2_session *session;
nghttp2_stream *stream;
session = (nghttp2_session*)ptr;
stream = (nghttp2_stream*)entry;
stream->remote_flow_control = 0;
/* If DATA frame is deferred due to flow control, push it back to
outbound queue. */
if(stream->deferred_data &&
(stream->deferred_flags & NGHTTP2_DEFERRED_FLOW_CONTROL)) {
int rv;
rv = nghttp2_pq_push(&session->ob_pq, stream->deferred_data);
if(rv == 0) {
nghttp2_stream_detach_deferred_data(stream);
} else {
/* FATAL */
assert(rv < NGHTTP2_ERR_FATAL);
return rv;
}
}
return 0;
}
/*
* Disable remote side connection-level flow control and stream-level
* flow control of existing streams.
*/
static int nghttp2_session_disable_remote_flow_control
(nghttp2_session *session)
{
session->remote_flow_control = 0;
return nghttp2_map_each(&session->streams,
nghttp2_disable_remote_flow_control_func, session);
}
static int nghttp2_disable_local_flow_control_func(nghttp2_map_entry *entry,
void *ptr)
{
nghttp2_stream *stream;
stream = (nghttp2_stream*)entry;
stream->local_flow_control = 0;
return 0;
}
/*
* Disable stream-level flow control in local side of existing
* streams.
*/
static void nghttp2_session_disable_local_flow_control
(nghttp2_session *session)
{
int rv;
session->local_flow_control = 0;
rv = nghttp2_map_each(&session->streams,
nghttp2_disable_local_flow_control_func, NULL);
assert(rv == 0);
}
int nghttp2_session_update_local_settings(nghttp2_session *session,
nghttp2_settings_entry *iv,
size_t niv)
2012-03-10 10:41:01 +01:00
{
int rv;
size_t i;
uint8_t old_flow_control =
session->local_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS];
uint8_t new_flow_control = old_flow_control;
int32_t new_initial_window_size = -1;
for(i = 0; i < niv; ++i) {
if(iv[i].settings_id == NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE) {
new_initial_window_size = iv[i].value;
} else if(iv[i].settings_id == NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS) {
new_flow_control = iv[i].value;
}
}
if(!old_flow_control && !new_flow_control && new_initial_window_size != -1) {
rv = nghttp2_session_update_local_initial_window_size
(session,
new_initial_window_size,
session->local_settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE]);
if(rv != 0) {
return rv;
}
}
2012-03-10 10:41:01 +01:00
for(i = 0; i < niv; ++i) {
2013-07-12 17:19:03 +02:00
assert(iv[i].settings_id > 0 && iv[i].settings_id <= NGHTTP2_SETTINGS_MAX);
2012-03-10 10:41:01 +01:00
session->local_settings[iv[i].settings_id] = iv[i].value;
}
if(old_flow_control == 0 &&
session->local_settings[NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS]) {
nghttp2_session_disable_local_flow_control(session);
}
return 0;
2012-03-10 10:41:01 +01:00
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_on_settings_received(nghttp2_session *session,
nghttp2_frame *frame)
{
int rv;
size_t i;
2013-07-12 17:19:03 +02:00
int check[NGHTTP2_SETTINGS_MAX+1];
if(frame->hd.stream_id != 0) {
return nghttp2_session_handle_invalid_connection(session, frame,
NGHTTP2_PROTOCOL_ERROR);
}
/* Check ID/value pairs and persist them if necessary. */
memset(check, 0, sizeof(check));
for(i = 0; i < frame->settings.niv; ++i) {
2013-07-12 17:19:03 +02:00
nghttp2_settings_entry *entry = &frame->settings.iv[i];
2013-07-15 14:45:59 +02:00
/* The spec says if the multiple values for the same ID were
found, use the first one and ignore the rest. */
2013-07-12 17:19:03 +02:00
if(entry->settings_id > NGHTTP2_SETTINGS_MAX || entry->settings_id == 0 ||
check[entry->settings_id] == 1) {
continue;
}
check[entry->settings_id] = 1;
switch(entry->settings_id) {
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) {
rv = nghttp2_session_update_remote_initial_window_size
(session, entry->value);
if(rv != 0) {
if(nghttp2_is_fatal(rv)) {
return rv;
} else {
return nghttp2_session_handle_invalid_connection
(session, frame, NGHTTP2_FLOW_CONTROL_ERROR);
}
}
2013-07-15 17:15:04 +02:00
} else {
return nghttp2_session_handle_invalid_connection
(session, frame, NGHTTP2_PROTOCOL_ERROR);
}
break;
case NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS:
if(entry->value & 0x1) {
2013-07-16 13:54:24 +02:00
if(session->remote_settings[entry->settings_id] == 0) {
rv = nghttp2_session_disable_remote_flow_control(session);
2013-07-16 13:54:24 +02:00
if(rv != 0) {
return rv;
}
}
2013-07-16 13:54:24 +02:00
} else if(session->remote_settings[entry->settings_id] == 1) {
/* Re-enabling flow control is subject to connection-level
error(?) */
return nghttp2_session_handle_invalid_connection
(session, frame, NGHTTP2_FLOW_CONTROL_ERROR);
}
break;
}
session->remote_settings[entry->settings_id] = entry->value;
}
2013-07-15 14:45:59 +02:00
nghttp2_session_call_on_frame_received(session, frame);
return 0;
}
2013-07-24 18:49:05 +02:00
int nghttp2_session_on_push_promise_received(nghttp2_session *session,
nghttp2_frame *frame)
{
nghttp2_stream *stream;
if(session->server || frame->hd.stream_id == 0) {
return nghttp2_session_handle_invalid_connection(session, frame,
NGHTTP2_PROTOCOL_ERROR);
}
if(session->goaway_flags) {
/* We just dicard PUSH_PROMISE after GOAWAY is sent or
received. */
return 0;
}
if(!nghttp2_session_is_new_peer_stream_id
(session, frame->push_promise.promised_stream_id)) {
/* The spec says if an endpoint receives a HEADERS with invalid
stream ID, it MUST issue connection error with error code
PROTOCOL_ERROR. It applies to PUSH_PROMISE too. */
return nghttp2_session_handle_invalid_connection
(session, frame, NGHTTP2_PROTOCOL_ERROR);
}
session->last_recv_stream_id = frame->push_promise.promised_stream_id;
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if(stream) {
if((stream->shut_flags & NGHTTP2_SHUT_RD) == 0) {
if(stream->state == NGHTTP2_STREAM_CLOSING) {
return nghttp2_session_add_rst_stream
(session,
frame->push_promise.promised_stream_id,
NGHTTP2_REFUSED_STREAM);
} else {
nghttp2_stream *promised_stream;
promised_stream = nghttp2_session_open_stream
(session,
frame->push_promise.promised_stream_id,
frame->hd.flags,
nghttp2_pushed_stream_pri(stream),
NGHTTP2_STREAM_RESERVED,
NULL);
if(!promised_stream) {
return NGHTTP2_ERR_NOMEM;
}
nghttp2_session_call_on_frame_received(session, frame);
}
} else {
if(session->callbacks.on_invalid_frame_recv_callback) {
session->callbacks.on_invalid_frame_recv_callback
(session, frame, NGHTTP2_PROTOCOL_ERROR, session->user_data);
}
return nghttp2_session_add_rst_stream
(session,
frame->push_promise.promised_stream_id,
NGHTTP2_PROTOCOL_ERROR);
}
} else {
return nghttp2_session_add_rst_stream
(session,
frame->push_promise.promised_stream_id,
NGHTTP2_REFUSED_STREAM);
}
return 0;
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_on_ping_received(nghttp2_session *session,
nghttp2_frame *frame)
2012-01-27 15:05:29 +01:00
{
int r = 0;
if(frame->hd.stream_id != 0) {
return nghttp2_session_handle_invalid_connection(session, frame,
NGHTTP2_PROTOCOL_ERROR);
}
2013-07-15 14:45:59 +02:00
if((frame->hd.flags & NGHTTP2_FLAG_PONG) == 0) {
/* Peer sent ping, so ping it back */
r = nghttp2_session_add_ping(session, NGHTTP2_FLAG_PONG,
frame->ping.opaque_data);
2012-01-27 15:05:29 +01:00
}
2013-07-15 14:45:59 +02:00
nghttp2_session_call_on_frame_received(session, frame);
2012-01-27 15:05:29 +01:00
return r;
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_on_goaway_received(nghttp2_session *session,
nghttp2_frame *frame)
2012-01-28 11:22:38 +01:00
{
2013-07-15 14:45:59 +02:00
session->last_stream_id = frame->goaway.last_stream_id;
2013-07-12 17:19:03 +02:00
session->goaway_flags |= NGHTTP2_GOAWAY_RECV;
2013-07-15 14:45:59 +02:00
nghttp2_session_call_on_frame_received(session, frame);
2012-01-28 11:22:38 +01:00
return 0;
}
2013-07-16 13:54:24 +02:00
static int nghttp2_push_back_deferred_data_func(nghttp2_map_entry *entry,
void *ptr)
{
nghttp2_session *session;
nghttp2_stream *stream;
session = (nghttp2_session*)ptr;
stream = (nghttp2_stream*)entry;
/* If DATA frame is deferred due to flow control, push it back to
outbound queue. */
if(stream->deferred_data &&
(stream->deferred_flags & NGHTTP2_DEFERRED_FLOW_CONTROL) &&
(stream->remote_flow_control == 0 || stream->remote_window_size > 0)) {
2013-07-16 13:54:24 +02:00
int rv;
rv = nghttp2_pq_push(&session->ob_pq, stream->deferred_data);
if(rv == 0) {
nghttp2_stream_detach_deferred_data(stream);
} else {
/* FATAL */
assert(rv < NGHTTP2_ERR_FATAL);
return rv;
}
}
return 0;
}
/*
* Push back deferred DATA frames to queue if they are deferred due to
* connection-level flow control.
*/
static int nghttp2_session_push_back_deferred_data(nghttp2_session *session)
{
return nghttp2_map_each(&session->streams,
nghttp2_push_back_deferred_data_func, session);
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_on_window_update_received(nghttp2_session *session,
nghttp2_frame *frame)
2012-02-25 16:12:32 +01:00
{
2013-07-15 14:45:59 +02:00
if(frame->hd.stream_id == 0) {
2013-07-16 13:54:24 +02:00
/* Handle connection-level flow control */
if(session->remote_flow_control == 0) {
/* The sepc says receiving WINDOW_UPDATE from peer when flow
control is disabled is error, but disabling flow control and
receiving WINDOW_UPDATE are asynchronous, so it is hard to
determine that the peer is misbehaving or not without
measuring RTT. For now, we just ignore such frames. */
2013-07-16 14:30:36 +02:00
nghttp2_session_call_on_frame_received(session, frame);
return 0;
}
if(frame->hd.flags & NGHTTP2_FLAG_END_FLOW_CONTROL) {
if(session->remote_flow_control) {
/* Disable connection-level flow control and push back
deferred DATA frame if any */
session->remote_flow_control = 0;
nghttp2_session_call_on_frame_received(session, frame);
return nghttp2_session_push_back_deferred_data(session);
}
2013-07-16 13:54:24 +02:00
return 0;
}
if(NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
session->remote_window_size) {
return nghttp2_session_handle_invalid_connection
(session, frame, NGHTTP2_FLOW_CONTROL_ERROR);
2013-07-16 13:54:24 +02:00
}
session->remote_window_size += frame->window_update.window_size_increment;
2013-07-16 13:54:24 +02:00
nghttp2_session_call_on_frame_received(session, frame);
/* To queue the DATA deferred by connection-level flow-control, we
have to check all streams. Bad. */
if(session->remote_window_size > 0) {
2013-07-16 13:54:24 +02:00
return nghttp2_session_push_back_deferred_data(session);
} else {
return 0;
}
2013-07-15 14:45:59 +02:00
} else {
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if(stream) {
2013-07-16 13:54:24 +02:00
if(stream->remote_flow_control == 0) {
/* Same reason with connection-level flow control */
2013-07-16 14:30:36 +02:00
nghttp2_session_call_on_frame_received(session, frame);
return 0;
}
if(frame->hd.flags & NGHTTP2_FLAG_END_FLOW_CONTROL) {
stream->remote_flow_control = 0;
if(stream->remote_flow_control &&
stream->deferred_data != NULL &&
(stream->deferred_flags & NGHTTP2_DEFERRED_FLOW_CONTROL)) {
int r;
r = nghttp2_pq_push(&session->ob_pq, stream->deferred_data);
if(r == 0) {
nghttp2_stream_detach_deferred_data(stream);
} else if(r < 0) {
/* FATAL */
assert(r < NGHTTP2_ERR_FATAL);
return r;
}
}
nghttp2_session_call_on_frame_received(session, frame);
2013-07-16 13:54:24 +02:00
return 0;
}
if(NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
stream->remote_window_size) {
2012-02-25 16:12:32 +01:00
int r;
2013-07-15 14:45:59 +02:00
r = nghttp2_session_handle_invalid_stream
(session, frame, NGHTTP2_FLOW_CONTROL_ERROR);
2013-07-15 14:45:59 +02:00
return r;
} else {
stream->remote_window_size +=
frame->window_update.window_size_increment;
if(stream->remote_window_size > 0 &&
2013-07-16 13:54:24 +02:00
(session->remote_flow_control == 0 ||
session->remote_window_size > 0) &&
2013-07-15 14:45:59 +02:00
stream->deferred_data != NULL &&
(stream->deferred_flags & NGHTTP2_DEFERRED_FLOW_CONTROL)) {
int r;
r = nghttp2_pq_push(&session->ob_pq, stream->deferred_data);
if(r == 0) {
nghttp2_stream_detach_deferred_data(stream);
} else if(r < 0) {
/* FATAL */
assert(r < NGHTTP2_ERR_FATAL);
return r;
}
2012-02-25 16:12:32 +01:00
}
2013-07-15 14:45:59 +02:00
nghttp2_session_call_on_frame_received(session, frame);
2012-02-25 16:12:32 +01:00
}
}
}
return 0;
}
2013-07-15 14:45:59 +02:00
static int nghttp2_get_status_code_from_error_code(int lib_error_code)
{
2013-07-15 14:45:59 +02:00
switch(lib_error_code) {
2013-07-12 17:19:03 +02:00
case(NGHTTP2_ERR_FRAME_TOO_LARGE):
return NGHTTP2_FRAME_TOO_LARGE;
default:
2013-07-12 17:19:03 +02:00
return NGHTTP2_PROTOCOL_ERROR;
}
}
/* For errors, this function only returns FATAL error. */
2013-07-12 17:19:03 +02:00
static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
{
int r = 0;
uint16_t type;
2013-07-12 17:19:03 +02:00
nghttp2_frame frame;
2013-07-15 14:45:59 +02:00
type = session->iframe.headbuf[2];
switch(type) {
2013-07-15 14:45:59 +02:00
case NGHTTP2_HEADERS:
if(session->iframe.error_code == 0) {
2013-07-15 14:45:59 +02:00
r = nghttp2_frame_unpack_headers(&frame.headers,
session->iframe.headbuf,
sizeof(session->iframe.headbuf),
session->iframe.buf,
session->iframe.buflen,
2013-07-19 17:08:14 +02:00
&session->hd_inflater);
2013-07-12 17:19:03 +02:00
} else if(session->iframe.error_code == NGHTTP2_ERR_FRAME_TOO_LARGE) {
2013-07-15 14:45:59 +02:00
r = nghttp2_frame_unpack_headers_without_nv
(&frame.headers,
session->iframe.headbuf, sizeof(session->iframe.headbuf),
session->iframe.buf, session->iframe.buflen);
if(r == 0) {
r = session->iframe.error_code;
}
} else {
r = session->iframe.error_code;
}
if(r == 0) {
2013-07-24 18:49:05 +02:00
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, frame.hd.stream_id);
if(stream) {
if(nghttp2_session_is_my_stream_id(session, frame.hd.stream_id)) {
2013-07-15 14:45:59 +02:00
if(stream->state == NGHTTP2_STREAM_OPENING) {
frame.headers.cat = NGHTTP2_HCAT_RESPONSE;
2013-07-25 14:07:38 +02:00
r = nghttp2_session_on_response_headers_received
(session, &frame, stream);
2013-07-15 14:45:59 +02:00
} else {
frame.headers.cat = NGHTTP2_HCAT_HEADERS;
r = nghttp2_session_on_headers_received(session, &frame, stream);
}
2013-07-24 18:49:05 +02:00
} else if(!session->server &&
stream->state == NGHTTP2_STREAM_RESERVED) {
frame.headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE;
2013-07-25 14:07:38 +02:00
r = nghttp2_session_on_push_response_headers_received
(session, &frame, stream);
2013-07-15 14:45:59 +02:00
} else {
2013-07-24 18:49:05 +02:00
frame.headers.cat = NGHTTP2_HCAT_HEADERS;
r = nghttp2_session_on_headers_received(session, &frame, stream);
2013-07-15 14:45:59 +02:00
}
} else {
frame.headers.cat = NGHTTP2_HCAT_REQUEST;
2013-07-25 14:07:38 +02:00
r = nghttp2_session_on_request_headers_received(session, &frame);
2013-07-12 17:19:03 +02:00
}
2013-07-15 14:45:59 +02:00
nghttp2_frame_headers_free(&frame.headers);
2013-07-19 17:08:14 +02:00
nghttp2_hd_end_headers(&session->hd_inflater);
} else if(r == NGHTTP2_ERR_INVALID_HEADER_BLOCK) {
2013-07-12 17:19:03 +02:00
r = nghttp2_session_handle_invalid_stream
(session, &frame, nghttp2_get_status_code_from_error_code(r));
/* TODO test this. It seems NGHTTP2_ERR_INVALID_HEADER_BLOCK is
not used in framing anymore. */
2013-07-15 14:45:59 +02:00
nghttp2_frame_headers_free(&frame.headers);
nghttp2_hd_end_headers(&session->hd_inflater);
2013-07-12 17:19:03 +02:00
} else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error(session, type, r,
NGHTTP2_PROTOCOL_ERROR);
}
break;
case NGHTTP2_PRIORITY:
r = nghttp2_frame_unpack_priority(&frame.priority,
session->iframe.headbuf,
sizeof(session->iframe.headbuf),
session->iframe.buf,
session->iframe.buflen);
if(r == 0) {
r = nghttp2_session_on_priority_received(session, &frame);
nghttp2_frame_priority_free(&frame.priority);
} else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error(session, type, r,
NGHTTP2_PROTOCOL_ERROR);
}
break;
2013-07-12 17:19:03 +02:00
case NGHTTP2_RST_STREAM:
r = nghttp2_frame_unpack_rst_stream(&frame.rst_stream,
2012-01-27 15:22:27 +01:00
session->iframe.headbuf,
sizeof(session->iframe.headbuf),
session->iframe.buf,
session->iframe.buflen);
2012-01-27 15:22:27 +01:00
if(r == 0) {
2013-07-12 17:19:03 +02:00
r = nghttp2_session_on_rst_stream_received(session, &frame);
nghttp2_frame_rst_stream_free(&frame.rst_stream);
} else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error(session, type, r,
NGHTTP2_PROTOCOL_ERROR);
2012-01-27 15:22:27 +01:00
}
break;
2013-07-12 17:19:03 +02:00
case NGHTTP2_SETTINGS:
r = nghttp2_frame_unpack_settings(&frame.settings,
session->iframe.headbuf,
sizeof(session->iframe.headbuf),
session->iframe.buf,
session->iframe.buflen);
if(r == 0) {
2013-07-12 17:19:03 +02:00
r = nghttp2_session_on_settings_received(session, &frame);
nghttp2_frame_settings_free(&frame.settings);
} else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error(session, type, r,
NGHTTP2_PROTOCOL_ERROR);
}
break;
2013-07-24 18:49:05 +02:00
case NGHTTP2_PUSH_PROMISE:
if(session->iframe.error_code == 0) {
r = nghttp2_frame_unpack_push_promise(&frame.push_promise,
session->iframe.headbuf,
sizeof(session->iframe.headbuf),
session->iframe.buf,
session->iframe.buflen,
&session->hd_inflater);
} else {
r = session->iframe.error_code;
}
if(r == 0) {
r = nghttp2_session_on_push_promise_received(session, &frame);
nghttp2_frame_push_promise_free(&frame.push_promise);
nghttp2_hd_end_headers(&session->hd_inflater);
} else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error(session, type, r,
NGHTTP2_PROTOCOL_ERROR);
}
break;
2013-07-12 17:19:03 +02:00
case NGHTTP2_PING:
r = nghttp2_frame_unpack_ping(&frame.ping,
2012-01-27 15:05:29 +01:00
session->iframe.headbuf,
sizeof(session->iframe.headbuf),
session->iframe.buf,
session->iframe.buflen);
2012-01-27 15:05:29 +01:00
if(r == 0) {
2013-07-12 17:19:03 +02:00
r = nghttp2_session_on_ping_received(session, &frame);
nghttp2_frame_ping_free(&frame.ping);
} else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error(session, type, r,
NGHTTP2_PROTOCOL_ERROR);
2012-01-27 15:05:29 +01:00
}
break;
2013-07-12 17:19:03 +02:00
case NGHTTP2_GOAWAY:
r = nghttp2_frame_unpack_goaway(&frame.goaway,
2012-01-28 11:22:38 +01:00
session->iframe.headbuf,
sizeof(session->iframe.headbuf),
session->iframe.buf,
session->iframe.buflen);
2012-01-28 11:22:38 +01:00
if(r == 0) {
2013-07-12 17:19:03 +02:00
r = nghttp2_session_on_goaway_received(session, &frame);
nghttp2_frame_goaway_free(&frame.goaway);
} else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error(session, type, r,
NGHTTP2_PROTOCOL_ERROR);
}
break;
2013-07-12 17:19:03 +02:00
case NGHTTP2_WINDOW_UPDATE:
r = nghttp2_frame_unpack_window_update(&frame.window_update,
2012-02-25 16:12:32 +01:00
session->iframe.headbuf,
sizeof(session->iframe.headbuf),
session->iframe.buf,
session->iframe.buflen);
2012-02-25 16:12:32 +01:00
if(r == 0) {
2013-07-12 17:19:03 +02:00
r = nghttp2_session_on_window_update_received(session, &frame);
nghttp2_frame_window_update_free(&frame.window_update);
} else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error(session, type, r,
NGHTTP2_PROTOCOL_ERROR);
2012-04-05 18:45:39 +02:00
}
break;
default:
/* Unknown frame */
2013-07-15 14:45:59 +02:00
if(session->callbacks.on_unknown_frame_recv_callback) {
session->callbacks.on_unknown_frame_recv_callback
(session,
session->iframe.headbuf,
sizeof(session->iframe.headbuf),
session->iframe.buf,
session->iframe.buflen,
session->user_data);
}
}
2013-07-12 17:19:03 +02:00
if(nghttp2_is_fatal(r)) {
return r;
} else {
return 0;
}
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_on_data_received(nghttp2_session *session,
2013-07-15 14:45:59 +02:00
uint16_t length, uint8_t flags,
int32_t stream_id)
{
int r = 0;
2013-07-15 14:45:59 +02:00
nghttp2_error_code error_code = 0;
2013-07-12 17:19:03 +02:00
nghttp2_stream *stream;
2013-07-15 14:45:59 +02:00
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. */
return nghttp2_session_fail_session(session, NGHTTP2_PROTOCOL_ERROR);
}
2013-07-12 17:19:03 +02:00
stream = nghttp2_session_get_stream(session, stream_id);
if(stream) {
2013-07-12 17:19:03 +02:00
if((stream->shut_flags & NGHTTP2_SHUT_RD) == 0) {
int valid = 0;
2013-07-12 17:19:03 +02:00
if(nghttp2_session_is_my_stream_id(session, stream_id)) {
if(stream->state == NGHTTP2_STREAM_OPENED) {
valid = 1;
if(session->callbacks.on_data_recv_callback) {
session->callbacks.on_data_recv_callback
2013-07-15 17:15:04 +02:00
(session, length, flags, stream_id, session->user_data);
}
2013-07-12 17:19:03 +02:00
} else if(stream->state != NGHTTP2_STREAM_CLOSING) {
2013-07-15 14:45:59 +02:00
error_code = NGHTTP2_PROTOCOL_ERROR;
}
2013-07-12 17:19:03 +02:00
} else if(stream->state != NGHTTP2_STREAM_CLOSING) {
/* It is OK if this is remote peer initiated stream and we did
2013-07-25 14:18:13 +02:00
not receive END_STREAM unless stream is in
NGHTTP2_STREAM_CLOSING state. This is a race condition. */
valid = 1;
if(session->callbacks.on_data_recv_callback) {
session->callbacks.on_data_recv_callback
2013-07-15 17:15:04 +02:00
(session, length, flags, stream_id, session->user_data);
}
2013-07-15 14:45:59 +02:00
if(flags & NGHTTP2_FLAG_END_STREAM) {
2013-07-12 17:19:03 +02:00
nghttp2_session_call_on_request_recv(session, stream_id);
}
}
if(valid) {
2013-07-15 14:45:59 +02:00
if(flags & NGHTTP2_FLAG_END_STREAM) {
2013-07-12 17:19:03 +02:00
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
nghttp2_session_close_stream_if_shut_rdwr(session, stream);
}
}
} else {
2013-07-15 14:45:59 +02:00
/* 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.
*/
error_code = NGHTTP2_STREAM_CLOSED;
}
} else {
2013-07-15 14:45:59 +02:00
error_code = NGHTTP2_PROTOCOL_ERROR;
}
2013-07-15 14:45:59 +02:00
if(error_code != 0) {
r = nghttp2_session_add_rst_stream(session, stream_id, error_code);
}
return r;
}
/* For errors, this function only returns FATAL error. */
2013-07-12 17:19:03 +02:00
static int nghttp2_session_process_data_frame(nghttp2_session *session)
{
int r;
2013-07-15 14:45:59 +02:00
nghttp2_frame_hd hd;
nghttp2_frame_unpack_frame_hd(&hd, session->iframe.headbuf);
r = nghttp2_session_on_data_received(session, hd.length, hd.flags,
hd.stream_id);
2013-07-12 17:19:03 +02:00
if(nghttp2_is_fatal(r)) {
return r;
} else {
return 0;
}
}
/*
* If the resulting recv_window_size is strictly larger than
* NGHTTP2_MAX_WINDOW_SIZE, return NGHTTP2_ERR_FLOW_CONTROL.
*/
static int adjust_recv_window_size(int32_t *recv_window_size_ptr,
int32_t delta)
2013-07-16 13:54:24 +02:00
{
if(*recv_window_size_ptr > NGHTTP2_MAX_WINDOW_SIZE - delta) {
return NGHTTP2_ERR_FLOW_CONTROL;
2013-07-16 13:54:24 +02:00
}
*recv_window_size_ptr += delta;
return 0;
2013-07-16 13:54:24 +02:00
}
2012-02-25 16:12:32 +01:00
/*
* 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_STREAM_WINDOW_UPDATE is set,
* WINDOW_UPDATE will not be sent.
2012-02-25 16:12:32 +01:00
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
2013-07-12 17:19:03 +02:00
* NGHTTP2_ERR_NOMEM
2012-02-25 16:12:32 +01:00
* Out of memory.
*/
static int nghttp2_session_update_recv_stream_window_size
(nghttp2_session *session,
nghttp2_stream *stream,
int32_t delta_size)
{
int rv;
rv = adjust_recv_window_size(&stream->recv_window_size, delta_size);
if(rv != 0) {
return nghttp2_session_add_rst_stream(session, stream->stream_id,
NGHTTP2_ERR_FLOW_CONTROL);
}
if(!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE)) {
/* We have to use local_settings here because it is the constraint
the remote endpoint should honor. */
if(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) {
stream->recv_window_size = 0;
} else {
return rv;
2012-02-25 16:12:32 +01:00
}
}
}
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_CONNECTION_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 nghttp2_session_update_recv_connection_window_size
(nghttp2_session *session,
int32_t delta_size)
{
int rv;
rv = adjust_recv_window_size(&session->recv_window_size, delta_size);
if(rv != 0) {
return nghttp2_session_fail_session(session, NGHTTP2_ERR_FLOW_CONTROL);
}
if(!(session->opt_flags &
NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE)) {
if(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) {
session->recv_window_size = 0;
} else {
return rv;
2013-07-16 13:54:24 +02:00
}
}
}
2012-02-25 16:12:32 +01:00
return 0;
}
/*
* Returns nonzero if the reception of DATA for stream |stream_id| is
* allowed.
*/
2013-07-12 17:19:03 +02:00
static int nghttp2_session_check_data_recv_allowed(nghttp2_session *session,
int32_t stream_id)
{
2013-07-12 17:19:03 +02:00
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, stream_id);
if(stream) {
2013-07-12 17:19:03 +02:00
if((stream->shut_flags & NGHTTP2_SHUT_RD) == 0) {
if(nghttp2_session_is_my_stream_id(session, stream_id)) {
if(stream->state == NGHTTP2_STREAM_OPENED) {
return 1;
}
2013-07-12 17:19:03 +02:00
} else if(stream->state != NGHTTP2_STREAM_CLOSING) {
/* It is OK if this is remote peer initiated stream and we did
2013-07-25 14:18:13 +02:00
not receive END_STREAM unless stream is in
NGHTTP2_STREAM_CLOSING state. This is a race condition. */
return 1;
}
}
}
return 0;
}
2013-07-12 17:19:03 +02:00
ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
const uint8_t *in, size_t inlen)
{
const uint8_t *inmark, *inlimit;
inmark = in;
inlimit = in+inlen;
while(1) {
ssize_t r;
2013-07-12 17:19:03 +02:00
if(session->iframe.state == NGHTTP2_RECV_HEAD) {
size_t remheadbytes;
size_t readlen;
size_t bufavail = inlimit-inmark;
if(bufavail == 0) {
break;
}
2013-07-15 14:45:59 +02:00
remheadbytes = NGHTTP2_FRAME_HEAD_LENGTH - session->iframe.headbufoff;
2013-07-12 17:19:03 +02:00
readlen = nghttp2_min(remheadbytes, bufavail);
memcpy(session->iframe.headbuf+session->iframe.headbufoff,
inmark, readlen);
inmark += readlen;
session->iframe.headbufoff += readlen;
2013-07-15 14:45:59 +02:00
if(session->iframe.headbufoff == NGHTTP2_FRAME_HEAD_LENGTH) {
2013-07-12 17:19:03 +02:00
session->iframe.state = NGHTTP2_RECV_PAYLOAD;
session->iframe.payloadlen =
2013-07-15 14:45:59 +02:00
nghttp2_get_uint16(&session->iframe.headbuf[0]);
if(!nghttp2_frame_is_data_frame(session->iframe.headbuf)) {
/* control frame */
2013-07-19 17:08:14 +02:00
ssize_t buflen = session->iframe.payloadlen;
session->iframe.buflen = buflen;
2013-07-12 17:19:03 +02:00
r = nghttp2_reserve_buffer(&session->iframe.buf,
&session->iframe.bufmax,
buflen);
if(r != 0) {
/* FATAL */
2013-07-12 17:19:03 +02:00
assert(r < NGHTTP2_ERR_FATAL);
return r;
}
} else {
/* Check stream is open. If it is not open or closing,
ignore payload. */
int32_t stream_id;
2013-07-15 14:45:59 +02:00
stream_id = nghttp2_get_uint32(&session->iframe.headbuf[4]) &
NGHTTP2_STREAM_ID_MASK;
2013-07-12 17:19:03 +02:00
if(!nghttp2_session_check_data_recv_allowed(session, stream_id)) {
session->iframe.state = NGHTTP2_RECV_PAYLOAD_IGN;
}
}
} else {
break;
}
}
2013-07-12 17:19:03 +02:00
if(session->iframe.state == NGHTTP2_RECV_PAYLOAD ||
session->iframe.state == NGHTTP2_RECV_PAYLOAD_IGN) {
size_t rempayloadlen;
size_t bufavail, readlen;
2012-02-25 16:12:32 +01:00
int32_t data_stream_id = 0;
2013-07-15 14:45:59 +02:00
uint8_t data_flags = NGHTTP2_FLAG_NONE;
rempayloadlen = session->iframe.payloadlen - session->iframe.off;
bufavail = inlimit - inmark;
if(rempayloadlen > 0 && bufavail == 0) {
break;
}
2013-07-12 17:19:03 +02:00
readlen = nghttp2_min(bufavail, rempayloadlen);
2013-07-19 17:08:14 +02:00
if(!nghttp2_frame_is_data_frame(session->iframe.headbuf)) {
2013-07-12 17:19:03 +02:00
if(session->iframe.state != NGHTTP2_RECV_PAYLOAD_IGN) {
memcpy(session->iframe.buf+session->iframe.off, inmark, readlen);
}
2012-02-25 16:12:32 +01:00
} else {
/* For data frame, We don't buffer data. Instead, just pass
received data to callback function. */
2013-07-15 14:45:59 +02:00
data_stream_id = nghttp2_get_uint32(&session->iframe.headbuf[4]) &
2013-07-12 17:19:03 +02:00
NGHTTP2_STREAM_ID_MASK;
2013-07-15 14:45:59 +02:00
data_flags = session->iframe.headbuf[3];
2013-07-12 17:19:03 +02:00
if(session->iframe.state != NGHTTP2_RECV_PAYLOAD_IGN) {
if(session->callbacks.on_data_chunk_recv_callback) {
session->callbacks.on_data_chunk_recv_callback(session,
data_flags,
data_stream_id,
inmark,
readlen,
session->user_data);
}
2012-02-25 16:12:32 +01:00
}
/* TODO We need on_ignored_data_chunk_recv_callback, for
ingored DATA frame, so that the application can calculate
connection-level window size. */
}
session->iframe.off += readlen;
inmark += readlen;
2012-02-25 16:12:32 +01:00
if(nghttp2_frame_is_data_frame(session->iframe.headbuf) && readlen > 0) {
if(session->local_flow_control) {
/* Update connection-level flow control window for ignored
DATA frame too */
r = nghttp2_session_update_recv_connection_window_size
(session, readlen);
2012-02-25 16:12:32 +01:00
if(r < 0) {
/* FATAL */
2013-07-12 17:19:03 +02:00
assert(r < NGHTTP2_ERR_FATAL);
2012-02-25 16:12:32 +01:00
return r;
}
}
if(session->iframe.state != NGHTTP2_RECV_PAYLOAD_IGN &&
(session->iframe.payloadlen != session->iframe.off ||
(data_flags & NGHTTP2_FLAG_END_STREAM) == 0)) {
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, data_stream_id);
if(stream && stream->local_flow_control) {
r = nghttp2_session_update_recv_stream_window_size
(session, stream, readlen);
if(r < 0) {
/* FATAL */
assert(r < NGHTTP2_ERR_FATAL);
return r;
}
}
}
2012-02-25 16:12:32 +01:00
}
if(session->iframe.payloadlen == session->iframe.off) {
2013-07-15 14:45:59 +02:00
if(!nghttp2_frame_is_data_frame(session->iframe.headbuf)) {
2013-07-12 17:19:03 +02:00
r = nghttp2_session_process_ctrl_frame(session);
} else {
2013-07-12 17:19:03 +02:00
r = nghttp2_session_process_data_frame(session);
}
if(r < 0) {
/* FATAL */
2013-07-12 17:19:03 +02:00
assert(r < NGHTTP2_ERR_FATAL);
return r;
}
2013-07-12 17:19:03 +02:00
nghttp2_inbound_frame_reset(&session->iframe);
}
}
}
return inmark-in;
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_recv(nghttp2_session *session)
{
2013-07-12 17:19:03 +02:00
uint8_t buf[NGHTTP2_INBOUND_BUFFER_LENGTH];
while(1) {
ssize_t readlen;
2013-07-12 17:19:03 +02:00
readlen = nghttp2_recv(session, buf, sizeof(buf));
if(readlen > 0) {
2013-07-12 17:19:03 +02:00
ssize_t proclen = nghttp2_session_mem_recv(session, buf, readlen);
if(proclen < 0) {
return proclen;
}
assert(proclen == readlen);
2013-07-12 17:19:03 +02:00
} else if(readlen == 0 || readlen == NGHTTP2_ERR_WOULDBLOCK) {
return 0;
2013-07-12 17:19:03 +02:00
} else if(readlen == NGHTTP2_ERR_EOF) {
return readlen;
} else if(readlen < 0) {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
}
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_want_read(nghttp2_session *session)
{
/* If these flags are set, we don't want to read. The application
should drop the connection. */
2013-07-12 17:19:03 +02:00
if((session->goaway_flags & NGHTTP2_GOAWAY_FAIL_ON_SEND) &&
(session->goaway_flags & NGHTTP2_GOAWAY_SEND)) {
return 0;
}
/* Unless GOAWAY is sent or received, we always want to read
2012-01-28 11:22:38 +01:00
incoming frames. After GOAWAY is sent or received, we are only
interested in active streams. */
2013-07-12 17:19:03 +02:00
return !session->goaway_flags || nghttp2_map_size(&session->streams) > 0;
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_want_write(nghttp2_session *session)
{
/* If these flags are set, we don't want to write any data. The
application should drop the connection. */
2013-07-12 17:19:03 +02:00
if((session->goaway_flags & NGHTTP2_GOAWAY_FAIL_ON_SEND) &&
(session->goaway_flags & NGHTTP2_GOAWAY_SEND)) {
return 0;
}
/*
* Unless GOAWAY is sent or received, we want to write frames if
2013-07-25 14:18:13 +02:00
* 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. After GOAWAY is sent or received, we want to write
* frames if there is pending ones AND there are active frames.
*/
2013-07-12 17:19:03 +02:00
return (session->aob.item != NULL || !nghttp2_pq_empty(&session->ob_pq) ||
(!nghttp2_pq_empty(&session->ob_ss_pq) &&
!nghttp2_session_is_outgoing_concurrent_streams_max(session))) &&
(!session->goaway_flags || nghttp2_map_size(&session->streams) > 0);
}
2013-07-15 14:45:59 +02:00
int nghttp2_session_add_ping(nghttp2_session *session, uint8_t flags,
uint8_t *opaque_data)
2012-01-27 15:05:29 +01:00
{
int r;
2013-07-12 17:19:03 +02:00
nghttp2_frame *frame;
frame = malloc(sizeof(nghttp2_frame));
2012-01-27 15:05:29 +01:00
if(frame == NULL) {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_NOMEM;
2012-01-27 15:05:29 +01:00
}
2013-07-15 14:45:59 +02:00
nghttp2_frame_ping_init(&frame->ping, flags, opaque_data);
r = nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL);
2012-01-27 15:05:29 +01:00
if(r != 0) {
2013-07-12 17:19:03 +02:00
nghttp2_frame_ping_free(&frame->ping);
2012-01-27 15:05:29 +01:00
free(frame);
}
return r;
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_add_goaway(nghttp2_session *session,
2013-07-15 14:45:59 +02:00
int32_t last_stream_id,
nghttp2_error_code error_code,
uint8_t *opaque_data, size_t opaque_data_len)
2012-01-28 11:22:38 +01:00
{
int r;
2013-07-12 17:19:03 +02:00
nghttp2_frame *frame;
2013-07-15 14:45:59 +02:00
uint8_t *opaque_data_copy = NULL;
if(opaque_data_len) {
if(opaque_data_len > UINT16_MAX - 8) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
opaque_data_copy = malloc(opaque_data_len);
if(opaque_data_copy == NULL) {
return NGHTTP2_ERR_NOMEM;
}
memcpy(opaque_data_copy, opaque_data, opaque_data_len);
}
2013-07-12 17:19:03 +02:00
frame = malloc(sizeof(nghttp2_frame));
2012-01-28 11:22:38 +01:00
if(frame == NULL) {
2013-07-15 14:45:59 +02:00
free(opaque_data_copy);
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_NOMEM;
2012-01-28 11:22:38 +01:00
}
2013-07-15 14:45:59 +02:00
nghttp2_frame_goaway_init(&frame->goaway, last_stream_id, error_code,
opaque_data_copy, opaque_data_len);
r = nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL);
2012-01-28 11:22:38 +01:00
if(r != 0) {
2013-07-12 17:19:03 +02:00
nghttp2_frame_goaway_free(&frame->goaway);
2012-01-28 11:22:38 +01:00
free(frame);
}
return r;
}
2013-07-15 14:45:59 +02:00
int nghttp2_session_add_window_update(nghttp2_session *session, uint8_t flags,
2012-02-25 16:12:32 +01:00
int32_t stream_id,
2013-07-15 14:45:59 +02:00
int32_t window_size_increment)
2012-02-25 16:12:32 +01:00
{
int r;
2013-07-12 17:19:03 +02:00
nghttp2_frame *frame;
frame = malloc(sizeof(nghttp2_frame));
2012-02-25 16:12:32 +01:00
if(frame == NULL) {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_NOMEM;
2012-02-25 16:12:32 +01:00
}
2013-07-15 14:45:59 +02:00
nghttp2_frame_window_update_init(&frame->window_update, flags,
stream_id, window_size_increment);
r = nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL);
2012-02-25 16:12:32 +01:00
if(r != 0) {
2013-07-12 17:19:03 +02:00
nghttp2_frame_window_update_free(&frame->window_update);
2012-02-25 16:12:32 +01:00
free(frame);
}
return r;
}
2013-07-12 17:19:03 +02:00
ssize_t nghttp2_session_pack_data(nghttp2_session *session,
uint8_t **buf_ptr, size_t *buflen_ptr,
size_t datamax,
2013-07-12 17:19:03 +02:00
nghttp2_data *frame)
{
ssize_t framelen = datamax+8, r;
int eof_flags;
uint8_t flags;
2013-07-12 17:19:03 +02:00
r = nghttp2_reserve_buffer(buf_ptr, buflen_ptr, framelen);
if(r != 0) {
return r;
}
eof_flags = 0;
r = frame->data_prd.read_callback
2013-07-15 14:45:59 +02:00
(session, frame->hd.stream_id, (*buf_ptr)+8, datamax,
&eof_flags, &frame->data_prd.source, session->user_data);
2013-07-12 17:19:03 +02:00
if(r == NGHTTP2_ERR_DEFERRED || r == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
return r;
} else if(r < 0 || datamax < (size_t)r) {
/* This is the error code when callback is failed. */
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
2013-07-15 14:45:59 +02:00
memset(*buf_ptr, 0, NGHTTP2_FRAME_HEAD_LENGTH);
nghttp2_put_uint16be(&(*buf_ptr)[0], r);
flags = 0;
if(eof_flags) {
frame->eof = 1;
2013-07-15 14:45:59 +02:00
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
flags |= NGHTTP2_FLAG_END_STREAM;
}
}
2013-07-15 14:45:59 +02:00
(*buf_ptr)[3] = flags;
nghttp2_put_uint32be(&(*buf_ptr)[4], frame->hd.stream_id);
return r+8;
}
2012-01-27 15:05:29 +01:00
2013-07-12 17:19:03 +02:00
void* nghttp2_session_get_stream_user_data(nghttp2_session *session,
int32_t stream_id)
{
2013-07-12 17:19:03 +02:00
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, stream_id);
if(stream) {
return stream->stream_user_data;
} else {
return NULL;
}
}
2013-07-12 17:19:03 +02:00
int nghttp2_session_resume_data(nghttp2_session *session, int32_t stream_id)
{
int r;
2013-07-12 17:19:03 +02:00
nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, stream_id);
2012-02-25 16:12:32 +01:00
if(stream == NULL || stream->deferred_data == NULL ||
2013-07-12 17:19:03 +02:00
(stream->deferred_flags & NGHTTP2_DEFERRED_FLOW_CONTROL)) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
2013-07-12 17:19:03 +02:00
r = nghttp2_pq_push(&session->ob_pq, stream->deferred_data);
if(r == 0) {
2013-07-12 17:19:03 +02:00
nghttp2_stream_detach_deferred_data(stream);
}
return r;
}
2012-02-25 16:12:32 +01:00
2013-07-12 17:19:03 +02:00
size_t nghttp2_session_get_outbound_queue_size(nghttp2_session *session)
{
2013-07-12 17:19:03 +02:00
return nghttp2_pq_size(&session->ob_pq)+nghttp2_pq_size(&session->ob_ss_pq);
}
2012-04-05 18:45:39 +02:00
2013-07-12 17:19:03 +02:00
int nghttp2_session_set_option(nghttp2_session *session,
int optname, void *optval, size_t optlen)
{
switch(optname) {
case NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE:
case NGHTTP2_OPT_NO_AUTO_CONNECTION_WINDOW_UPDATE: {
int flag;
if(optname == NGHTTP2_OPT_NO_AUTO_STREAM_WINDOW_UPDATE) {
flag = NGHTTP2_OPTMASK_NO_AUTO_STREAM_WINDOW_UPDATE;
} else {
flag = NGHTTP2_OPTMASK_NO_AUTO_CONNECTION_WINDOW_UPDATE;
}
if(optlen == sizeof(int)) {
int intval = *(int*)optval;
if(intval) {
session->opt_flags |= flag;
} else {
session->opt_flags &= ~flag;
}
} else {
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
break;
}
default:
2013-07-12 17:19:03 +02:00
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
return 0;
}
2013-08-03 11:05:14 +02:00
int nghttp2_session_upgrade(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;
int max_conn_val_seen = 0;
int ini_win_size_seen = 0;
size_t i;
if((!session->server && session->next_stream_id != 1) ||
(session->server && session->last_recv_stream_id >= 1)) {
return NGHTTP2_ERR_PROTO;
}
if(settings_payloadlen % 8) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
rv = nghttp2_frame_unpack_settings_payload(&iv, &niv, settings_payload,
settings_payloadlen);
if(rv != 0) {
return rv;
}
for(i = 0; i < niv; ++i) {
if(iv[i].settings_id == NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS) {
max_conn_val_seen = 1;
} else if(iv[i].settings_id == NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE) {
ini_win_size_seen = 1;
}
}
if(!max_conn_val_seen || !ini_win_size_seen) {
free(iv);
return NGHTTP2_ERR_PROTO;
}
if(session->server) {
memset(&frame.hd, 0, sizeof(frame.hd));
frame.settings.iv = iv;
frame.settings.niv = niv;
rv = nghttp2_session_on_settings_received(session, &frame);
} else {
rv = nghttp2_submit_settings(session, iv, niv);
}
free(iv);
if(rv != 0) {
return rv;
}
stream = nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_END_STREAM,
0, NGHTTP2_STREAM_OPENING,
session->server ?
NULL : stream_user_data);
if(stream == NULL) {
return NGHTTP2_ERR_NOMEM;
}
if(session->server) {
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
session->last_recv_stream_id = 1;
} else {
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
session->next_stream_id += 2;
}
return 0;
}