mirror of
https://github.com/moparisthebest/spdylay
synced 2024-12-22 15:48:53 -05:00
1704 lines
56 KiB
C
1704 lines
56 KiB
C
/*
|
|
* Spdylay - SPDY Library
|
|
*
|
|
* Copyright (c) 2012 Tatsuhiro Tsujikawa
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
#include "spdylay_session.h"
|
|
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include "spdylay_helper.h"
|
|
|
|
/*
|
|
* Returns non-zero if the number of opened streams is larger than or
|
|
* equal to SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS value.
|
|
*/
|
|
static int spdylay_session_get_max_concurrent_streams_reached
|
|
(spdylay_session *session)
|
|
{
|
|
return session->settings[SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS]
|
|
<= spdylay_map_size(&session->streams);
|
|
}
|
|
|
|
int spdylay_session_is_my_stream_id(spdylay_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);
|
|
}
|
|
|
|
spdylay_stream* spdylay_session_get_stream(spdylay_session *session,
|
|
int32_t stream_id)
|
|
{
|
|
return (spdylay_stream*)spdylay_map_find(&session->streams, stream_id);
|
|
}
|
|
|
|
static int spdylay_outbound_item_compar(const void *lhsx, const void *rhsx)
|
|
{
|
|
const spdylay_outbound_item *lhs, *rhs;
|
|
lhs = (const spdylay_outbound_item*)lhsx;
|
|
rhs = (const spdylay_outbound_item*)rhsx;
|
|
if(lhs->pri == rhs->pri) {
|
|
return (lhs->seq < rhs->seq) ? -1 : ((lhs->seq > rhs->seq) ? 1 : 0);
|
|
} else {
|
|
return lhs->pri-rhs->pri;
|
|
}
|
|
}
|
|
|
|
static int spdylay_session_new(spdylay_session **session_ptr,
|
|
const spdylay_session_callbacks *callbacks,
|
|
void *user_data)
|
|
{
|
|
int r;
|
|
*session_ptr = malloc(sizeof(spdylay_session));
|
|
if(*session_ptr == NULL) {
|
|
goto fail_session;
|
|
}
|
|
memset(*session_ptr, 0, sizeof(spdylay_session));
|
|
|
|
/* next_stream_id, last_recv_stream_id and next_unique_id are
|
|
initialized in either spdylay_session_client_new or
|
|
spdylay_session_server_new */
|
|
|
|
(*session_ptr)->last_ping_unique_id = 0;
|
|
|
|
(*session_ptr)->next_seq = 0;
|
|
|
|
(*session_ptr)->goaway_flags = SPDYLAY_GOAWAY_NONE;
|
|
(*session_ptr)->last_good_stream_id = 0;
|
|
|
|
r = spdylay_zlib_deflate_hd_init(&(*session_ptr)->hd_deflater);
|
|
if(r != 0) {
|
|
goto fail_hd_deflater;
|
|
}
|
|
r = spdylay_zlib_inflate_hd_init(&(*session_ptr)->hd_inflater);
|
|
if(r != 0) {
|
|
goto fail_hd_inflater;
|
|
}
|
|
r = spdylay_map_init(&(*session_ptr)->streams);
|
|
if(r != 0) {
|
|
goto fail_streams;
|
|
}
|
|
r = spdylay_pq_init(&(*session_ptr)->ob_pq, spdylay_outbound_item_compar);
|
|
if(r != 0) {
|
|
goto fail_ob_pq;
|
|
}
|
|
r = spdylay_pq_init(&(*session_ptr)->ob_ss_pq, spdylay_outbound_item_compar);
|
|
if(r != 0) {
|
|
goto fail_ob_ss_pq;
|
|
}
|
|
|
|
(*session_ptr)->aob.framebuf = malloc
|
|
(SPDYLAY_INITIAL_OUTBOUND_FRAMEBUF_LENGTH);
|
|
if((*session_ptr)->aob.framebuf == NULL) {
|
|
goto fail_aob_framebuf;
|
|
}
|
|
(*session_ptr)->aob.framebufmax = SPDYLAY_INITIAL_OUTBOUND_FRAMEBUF_LENGTH;
|
|
|
|
(*session_ptr)->nvbuf = malloc(SPDYLAY_INITIAL_NV_BUFFER_LENGTH);
|
|
if((*session_ptr)->nvbuf == NULL) {
|
|
goto fail_nvbuf;
|
|
}
|
|
(*session_ptr)->nvbuflen = SPDYLAY_INITIAL_NV_BUFFER_LENGTH;
|
|
|
|
spdylay_buffer_init(&(*session_ptr)->inflatebuf, 4096);
|
|
|
|
memset((*session_ptr)->settings, 0, sizeof((*session_ptr)->settings));
|
|
(*session_ptr)->settings[SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS] =
|
|
SPDYLAY_CONCURRENT_STREAMS_MAX;
|
|
|
|
(*session_ptr)->callbacks = *callbacks;
|
|
(*session_ptr)->user_data = user_data;
|
|
|
|
(*session_ptr)->ibuf.mark = (*session_ptr)->ibuf.buf;
|
|
(*session_ptr)->ibuf.limit = (*session_ptr)->ibuf.buf;
|
|
|
|
(*session_ptr)->iframe.state = SPDYLAY_RECV_HEAD;
|
|
(*session_ptr)->iframe.buf = malloc(SPDYLAY_INITIAL_INBOUND_FRAMEBUF_LENGTH);
|
|
if((*session_ptr)->iframe.buf == NULL) {
|
|
goto fail_iframe_buf;
|
|
}
|
|
(*session_ptr)->iframe.bufmax = SPDYLAY_INITIAL_INBOUND_FRAMEBUF_LENGTH;
|
|
|
|
return 0;
|
|
|
|
fail_iframe_buf:
|
|
free((*session_ptr)->nvbuf);
|
|
fail_nvbuf:
|
|
free((*session_ptr)->aob.framebuf);
|
|
fail_aob_framebuf:
|
|
spdylay_pq_free(&(*session_ptr)->ob_ss_pq);
|
|
fail_ob_ss_pq:
|
|
spdylay_pq_free(&(*session_ptr)->ob_pq);
|
|
fail_ob_pq:
|
|
spdylay_map_free(&(*session_ptr)->streams);
|
|
fail_streams:
|
|
spdylay_zlib_inflate_free(&(*session_ptr)->hd_inflater);
|
|
fail_hd_inflater:
|
|
spdylay_zlib_deflate_free(&(*session_ptr)->hd_deflater);
|
|
fail_hd_deflater:
|
|
free(*session_ptr);
|
|
fail_session:
|
|
return SPDYLAY_ERR_NOMEM;
|
|
}
|
|
|
|
int spdylay_session_client_new(spdylay_session **session_ptr,
|
|
const spdylay_session_callbacks *callbacks,
|
|
void *user_data)
|
|
{
|
|
int r;
|
|
r = spdylay_session_new(session_ptr, callbacks, user_data);
|
|
if(r == 0) {
|
|
/* IDs for use in client */
|
|
(*session_ptr)->next_stream_id = 1;
|
|
(*session_ptr)->last_recv_stream_id = 0;
|
|
(*session_ptr)->next_unique_id = 1;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int spdylay_session_server_new(spdylay_session **session_ptr,
|
|
const spdylay_session_callbacks *callbacks,
|
|
void *user_data)
|
|
{
|
|
int r;
|
|
r = spdylay_session_new(session_ptr, callbacks, user_data);
|
|
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;
|
|
(*session_ptr)->next_unique_id = 2;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static void spdylay_free_streams(key_type key, void *val)
|
|
{
|
|
spdylay_stream_free((spdylay_stream*)val);
|
|
free(val);
|
|
}
|
|
|
|
void spdylay_outbound_item_free(spdylay_outbound_item *item)
|
|
{
|
|
if(item == NULL) {
|
|
return;
|
|
}
|
|
switch(item->frame_type) {
|
|
case SPDYLAY_SYN_STREAM:
|
|
spdylay_frame_syn_stream_free(&item->frame->syn_stream);
|
|
free(((spdylay_syn_stream_aux_data*)item->aux_data)->data_prd);
|
|
break;
|
|
case SPDYLAY_SYN_REPLY:
|
|
spdylay_frame_syn_reply_free(&item->frame->syn_reply);
|
|
break;
|
|
case SPDYLAY_RST_STREAM:
|
|
spdylay_frame_rst_stream_free(&item->frame->rst_stream);
|
|
break;
|
|
case SPDYLAY_SETTINGS:
|
|
spdylay_frame_settings_free(&item->frame->settings);
|
|
break;
|
|
case SPDYLAY_NOOP:
|
|
/* We don't have any public API to add NOOP, so here is
|
|
unreachable. */
|
|
abort();
|
|
case SPDYLAY_PING:
|
|
spdylay_frame_ping_free(&item->frame->ping);
|
|
break;
|
|
case SPDYLAY_GOAWAY:
|
|
spdylay_frame_goaway_free(&item->frame->goaway);
|
|
break;
|
|
case SPDYLAY_HEADERS:
|
|
spdylay_frame_headers_free(&item->frame->headers);
|
|
break;
|
|
case SPDYLAY_DATA:
|
|
spdylay_frame_data_free(&item->frame->data);
|
|
break;
|
|
}
|
|
free(item->frame);
|
|
free(item->aux_data);
|
|
}
|
|
|
|
static void spdylay_session_ob_pq_free(spdylay_pq *pq)
|
|
{
|
|
while(!spdylay_pq_empty(pq)) {
|
|
spdylay_outbound_item *item = (spdylay_outbound_item*)spdylay_pq_top(pq);
|
|
spdylay_outbound_item_free(item);
|
|
free(item);
|
|
spdylay_pq_pop(pq);
|
|
}
|
|
spdylay_pq_free(pq);
|
|
}
|
|
|
|
void spdylay_session_del(spdylay_session *session)
|
|
{
|
|
if(session == NULL) {
|
|
return;
|
|
}
|
|
spdylay_map_each(&session->streams, spdylay_free_streams);
|
|
spdylay_map_free(&session->streams);
|
|
spdylay_session_ob_pq_free(&session->ob_pq);
|
|
spdylay_session_ob_pq_free(&session->ob_ss_pq);
|
|
spdylay_zlib_deflate_free(&session->hd_deflater);
|
|
spdylay_zlib_inflate_free(&session->hd_inflater);
|
|
free(session->aob.framebuf);
|
|
free(session->nvbuf);
|
|
spdylay_buffer_free(&session->inflatebuf);
|
|
free(session->iframe.buf);
|
|
free(session);
|
|
}
|
|
|
|
int spdylay_session_add_frame(spdylay_session *session,
|
|
spdylay_frame_type frame_type,
|
|
spdylay_frame *frame,
|
|
void *aux_data)
|
|
{
|
|
int r;
|
|
spdylay_outbound_item *item;
|
|
item = malloc(sizeof(spdylay_outbound_item));
|
|
if(item == NULL) {
|
|
return SPDYLAY_ERR_NOMEM;
|
|
}
|
|
item->frame_type = frame_type;
|
|
item->frame = frame;
|
|
item->aux_data = aux_data;
|
|
item->seq = session->next_seq++;
|
|
/* Set priority lowest at the moment. */
|
|
item->pri = 3;
|
|
switch(frame_type) {
|
|
case SPDYLAY_SYN_STREAM:
|
|
item->pri = frame->syn_stream.pri;
|
|
break;
|
|
case SPDYLAY_SYN_REPLY: {
|
|
spdylay_stream *stream = spdylay_session_get_stream
|
|
(session, frame->syn_reply.stream_id);
|
|
if(stream) {
|
|
item->pri = stream->pri;
|
|
}
|
|
break;
|
|
}
|
|
case SPDYLAY_RST_STREAM: {
|
|
spdylay_stream *stream = spdylay_session_get_stream
|
|
(session, frame->rst_stream.stream_id);
|
|
if(stream) {
|
|
stream->state = SPDYLAY_STREAM_CLOSING;
|
|
item->pri = stream->pri;
|
|
}
|
|
break;
|
|
}
|
|
case SPDYLAY_SETTINGS:
|
|
/* Should SPDYLAY_SETTINGS have higher priority? */
|
|
break;
|
|
case SPDYLAY_NOOP:
|
|
/* We don't have any public API to add NOOP, so here is
|
|
unreachable. */
|
|
abort();
|
|
case SPDYLAY_PING:
|
|
/* Ping has "height" priority. Give it -1. */
|
|
item->pri = -1;
|
|
break;
|
|
case SPDYLAY_GOAWAY:
|
|
/* Should GOAWAY have higher priority? */
|
|
break;
|
|
case SPDYLAY_HEADERS:
|
|
/* Currently we don't have any API to send HEADERS frame, so this
|
|
is unreachable. */
|
|
abort();
|
|
case SPDYLAY_DATA: {
|
|
spdylay_stream *stream = spdylay_session_get_stream
|
|
(session, frame->data.stream_id);
|
|
if(stream) {
|
|
item->pri = stream->pri;
|
|
}
|
|
break;
|
|
}
|
|
};
|
|
if(frame_type == SPDYLAY_SYN_STREAM) {
|
|
r = spdylay_pq_push(&session->ob_ss_pq, item);
|
|
} else {
|
|
r = spdylay_pq_push(&session->ob_pq, item);
|
|
}
|
|
if(r != 0) {
|
|
free(item);
|
|
return r;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int spdylay_session_add_rst_stream(spdylay_session *session,
|
|
int32_t stream_id, uint32_t status_code)
|
|
{
|
|
int r;
|
|
spdylay_frame *frame;
|
|
frame = malloc(sizeof(spdylay_frame));
|
|
if(frame == NULL) {
|
|
return SPDYLAY_ERR_NOMEM;
|
|
}
|
|
spdylay_frame_rst_stream_init(&frame->rst_stream, stream_id, status_code);
|
|
r = spdylay_session_add_frame(session, SPDYLAY_RST_STREAM, frame, NULL);
|
|
if(r != 0) {
|
|
spdylay_frame_rst_stream_free(&frame->rst_stream);
|
|
free(frame);
|
|
return r;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
spdylay_stream* spdylay_session_open_stream(spdylay_session *session,
|
|
int32_t stream_id,
|
|
uint8_t flags, uint8_t pri,
|
|
spdylay_stream_state initial_state,
|
|
void *stream_user_data)
|
|
{
|
|
int r;
|
|
spdylay_stream *stream = malloc(sizeof(spdylay_stream));
|
|
if(stream == NULL) {
|
|
return NULL;
|
|
}
|
|
spdylay_stream_init(stream, stream_id, flags, pri, initial_state,
|
|
stream_user_data);
|
|
r = spdylay_map_insert(&session->streams, stream_id, stream);
|
|
if(r != 0) {
|
|
free(stream);
|
|
stream = NULL;
|
|
}
|
|
return stream;
|
|
}
|
|
|
|
int spdylay_session_close_stream(spdylay_session *session, int32_t stream_id,
|
|
spdylay_status_code status_code)
|
|
{
|
|
spdylay_stream *stream = spdylay_session_get_stream(session, stream_id);
|
|
if(stream) {
|
|
if(stream->state != SPDYLAY_STREAM_INITIAL &&
|
|
session->callbacks.on_stream_close_callback) {
|
|
session->callbacks.on_stream_close_callback(session, stream_id,
|
|
status_code,
|
|
session->user_data);
|
|
}
|
|
spdylay_map_erase(&session->streams, stream_id);
|
|
spdylay_stream_free(stream);
|
|
free(stream);
|
|
return 0;
|
|
} else {
|
|
return SPDYLAY_ERR_INVALID_ARGUMENT;
|
|
}
|
|
}
|
|
|
|
int spdylay_session_close_stream_if_shut_rdwr(spdylay_session *session,
|
|
spdylay_stream *stream)
|
|
{
|
|
if((stream->shut_flags & SPDYLAY_SHUT_RDWR) == SPDYLAY_SHUT_RDWR) {
|
|
return spdylay_session_close_stream(session, stream->stream_id,
|
|
SPDYLAY_OK);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void spdylay_session_close_pushed_streams(spdylay_session *session,
|
|
int32_t stream_id,
|
|
spdylay_status_code status_code)
|
|
{
|
|
spdylay_stream *stream;
|
|
stream = spdylay_session_get_stream(session, stream_id);
|
|
if(stream) {
|
|
int i;
|
|
for(i = 0; i < stream->pushed_streams_length; ++i) {
|
|
spdylay_session_close_stream(session, stream->pushed_streams[i],
|
|
status_code);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns non-zero value if local peer can send SYN_REPLY with stream
|
|
* ID |stream_id| at the moment, or 0.
|
|
*/
|
|
static int spdylay_session_is_reply_allowed(spdylay_session *session,
|
|
int32_t stream_id)
|
|
{
|
|
spdylay_stream *stream = spdylay_session_get_stream(session, stream_id);
|
|
if(stream == NULL) {
|
|
return 0;
|
|
}
|
|
if(spdylay_session_is_my_stream_id(session, stream_id)) {
|
|
return 0;
|
|
} else {
|
|
return stream->state == SPDYLAY_STREAM_OPENING &&
|
|
(stream->shut_flags & SPDYLAY_SHUT_WR) == 0;
|
|
}
|
|
}
|
|
|
|
static int spdylay_session_is_data_allowed(spdylay_session *session,
|
|
int32_t stream_id)
|
|
{
|
|
spdylay_stream *stream = spdylay_session_get_stream(session, stream_id);
|
|
if(stream == NULL) {
|
|
return 0;
|
|
}
|
|
if(spdylay_session_is_my_stream_id(session, stream_id)) {
|
|
/* If stream->state is SPDYLAY_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. */
|
|
return stream->state != SPDYLAY_STREAM_CLOSING &&
|
|
(stream->shut_flags & SPDYLAY_SHUT_WR) == 0;
|
|
} else {
|
|
return stream->state == SPDYLAY_STREAM_OPENED &&
|
|
(stream->shut_flags & SPDYLAY_SHUT_WR) == 0;
|
|
}
|
|
}
|
|
|
|
ssize_t spdylay_session_prep_frame(spdylay_session *session,
|
|
spdylay_outbound_item *item)
|
|
{
|
|
/* TODO Get or validate stream ID here */
|
|
/* TODO Validate assoc_stream_id here */
|
|
ssize_t framebuflen;
|
|
switch(item->frame_type) {
|
|
case SPDYLAY_SYN_STREAM: {
|
|
uint32_t stream_id;
|
|
spdylay_syn_stream_aux_data *aux_data;
|
|
if(session->goaway_flags) {
|
|
/* When GOAWAY is sent or received, peer must not send new
|
|
SYN_STREAM. */
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
stream_id = session->next_stream_id;
|
|
item->frame->syn_stream.stream_id = stream_id;
|
|
session->next_stream_id += 2;
|
|
framebuflen = spdylay_frame_pack_syn_stream(&session->aob.framebuf,
|
|
&session->aob.framebufmax,
|
|
&session->nvbuf,
|
|
&session->nvbuflen,
|
|
&item->frame->syn_stream,
|
|
&session->hd_deflater);
|
|
if(framebuflen < 0) {
|
|
return framebuflen;
|
|
}
|
|
aux_data = (spdylay_syn_stream_aux_data*)item->aux_data;
|
|
if(spdylay_session_open_stream(session, stream_id,
|
|
item->frame->syn_stream.hd.flags,
|
|
item->frame->syn_stream.pri,
|
|
SPDYLAY_STREAM_INITIAL,
|
|
aux_data->stream_user_data) == NULL) {
|
|
return SPDYLAY_ERR_NOMEM;
|
|
}
|
|
break;
|
|
}
|
|
case SPDYLAY_SYN_REPLY: {
|
|
if(!spdylay_session_is_reply_allowed(session,
|
|
item->frame->syn_reply.stream_id)) {
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
framebuflen = spdylay_frame_pack_syn_reply(&session->aob.framebuf,
|
|
&session->aob.framebufmax,
|
|
&session->nvbuf,
|
|
&session->nvbuflen,
|
|
&item->frame->syn_reply,
|
|
&session->hd_deflater);
|
|
if(framebuflen < 0) {
|
|
return framebuflen;
|
|
}
|
|
break;
|
|
}
|
|
case SPDYLAY_RST_STREAM:
|
|
framebuflen = spdylay_frame_pack_rst_stream(&session->aob.framebuf,
|
|
&session->aob.framebufmax,
|
|
&item->frame->rst_stream);
|
|
if(framebuflen < 0) {
|
|
return framebuflen;
|
|
}
|
|
break;
|
|
case SPDYLAY_SETTINGS:
|
|
framebuflen = spdylay_frame_pack_settings(&session->aob.framebuf,
|
|
&session->aob.framebufmax,
|
|
&item->frame->settings);
|
|
if(framebuflen < 0) {
|
|
return framebuflen;
|
|
}
|
|
break;
|
|
case SPDYLAY_NOOP:
|
|
/* We don't have any public API to add NOOP, so here is
|
|
unreachable. */
|
|
abort();
|
|
case SPDYLAY_PING:
|
|
framebuflen = spdylay_frame_pack_ping(&session->aob.framebuf,
|
|
&session->aob.framebufmax,
|
|
&item->frame->ping);
|
|
if(framebuflen < 0) {
|
|
return framebuflen;
|
|
}
|
|
break;
|
|
case SPDYLAY_HEADERS:
|
|
/* Currently we don't have any API to send HEADERS frame, so this
|
|
is unreachable. */
|
|
abort();
|
|
case SPDYLAY_GOAWAY:
|
|
if(session->goaway_flags & SPDYLAY_GOAWAY_SEND) {
|
|
/* TODO The spec does not mandate that both endpoints have to
|
|
exchange GOAWAY. This implementation allows receiver of first
|
|
GOAWAY can sent its own GOAWAY to tell the remote peer that
|
|
last-good-stream-id. */
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
framebuflen = spdylay_frame_pack_goaway(&session->aob.framebuf,
|
|
&session->aob.framebufmax,
|
|
&item->frame->goaway);
|
|
if(framebuflen < 0) {
|
|
return framebuflen;
|
|
}
|
|
break;
|
|
case SPDYLAY_DATA: {
|
|
if(!spdylay_session_is_data_allowed(session, item->frame->data.stream_id)) {
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
framebuflen = spdylay_session_pack_data(session,
|
|
&session->aob.framebuf,
|
|
&session->aob.framebufmax,
|
|
&item->frame->data);
|
|
if(framebuflen < 0) {
|
|
return framebuflen;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
framebuflen = SPDYLAY_ERR_INVALID_ARGUMENT;
|
|
}
|
|
return framebuflen;
|
|
}
|
|
|
|
static void spdylay_active_outbound_item_reset
|
|
(spdylay_active_outbound_item *aob)
|
|
{
|
|
spdylay_outbound_item_free(aob->item);
|
|
free(aob->item);
|
|
aob->item = NULL;
|
|
aob->framebuflen = aob->framebufoff = 0;
|
|
}
|
|
|
|
spdylay_outbound_item* spdylay_session_get_ob_pq_top
|
|
(spdylay_session *session)
|
|
{
|
|
return (spdylay_outbound_item*)spdylay_pq_top(&session->ob_pq);
|
|
}
|
|
|
|
spdylay_outbound_item* spdylay_session_get_next_ob_item
|
|
(spdylay_session *session)
|
|
{
|
|
if(spdylay_pq_empty(&session->ob_pq)) {
|
|
if(spdylay_pq_empty(&session->ob_ss_pq)) {
|
|
return NULL;
|
|
} else {
|
|
/* Return item only when concurrent connection limit is not
|
|
reached */
|
|
if(spdylay_session_get_max_concurrent_streams_reached(session)) {
|
|
return NULL;
|
|
} else {
|
|
return spdylay_pq_top(&session->ob_ss_pq);
|
|
}
|
|
}
|
|
} else {
|
|
if(spdylay_pq_empty(&session->ob_ss_pq)) {
|
|
return spdylay_pq_top(&session->ob_pq);
|
|
} else {
|
|
spdylay_outbound_item *item, *syn_stream_item;
|
|
item = spdylay_pq_top(&session->ob_pq);
|
|
syn_stream_item = spdylay_pq_top(&session->ob_ss_pq);
|
|
if(spdylay_session_get_max_concurrent_streams_reached(session) ||
|
|
item->pri < syn_stream_item->pri ||
|
|
(item->pri == syn_stream_item->pri &&
|
|
item->seq < syn_stream_item->seq)) {
|
|
return item;
|
|
} else {
|
|
return syn_stream_item;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
spdylay_outbound_item* spdylay_session_pop_next_ob_item
|
|
(spdylay_session *session)
|
|
{
|
|
if(spdylay_pq_empty(&session->ob_pq)) {
|
|
if(spdylay_pq_empty(&session->ob_ss_pq)) {
|
|
return NULL;
|
|
} else {
|
|
/* Pop item only when concurrent connection limit is not
|
|
reached */
|
|
if(spdylay_session_get_max_concurrent_streams_reached(session)) {
|
|
return NULL;
|
|
} else {
|
|
spdylay_outbound_item *item;
|
|
item = spdylay_pq_top(&session->ob_ss_pq);
|
|
spdylay_pq_pop(&session->ob_ss_pq);
|
|
return item;
|
|
}
|
|
}
|
|
} else {
|
|
if(spdylay_pq_empty(&session->ob_ss_pq)) {
|
|
spdylay_outbound_item *item;
|
|
item = spdylay_pq_top(&session->ob_pq);
|
|
spdylay_pq_pop(&session->ob_pq);
|
|
return item;
|
|
} else {
|
|
spdylay_outbound_item *item, *syn_stream_item;
|
|
item = spdylay_pq_top(&session->ob_pq);
|
|
syn_stream_item = spdylay_pq_top(&session->ob_ss_pq);
|
|
if(spdylay_session_get_max_concurrent_streams_reached(session) ||
|
|
item->pri < syn_stream_item->pri ||
|
|
(item->pri == syn_stream_item->pri &&
|
|
item->seq < syn_stream_item->seq)) {
|
|
spdylay_pq_pop(&session->ob_pq);
|
|
return item;
|
|
} else {
|
|
spdylay_pq_pop(&session->ob_ss_pq);
|
|
return syn_stream_item;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int spdylay_session_after_frame_sent(spdylay_session *session)
|
|
{
|
|
/* TODO handle FIN flag. */
|
|
spdylay_outbound_item *item = session->aob.item;
|
|
spdylay_frame *frame = session->aob.item->frame;
|
|
spdylay_frame_type type = session->aob.item->frame_type;
|
|
if(type == SPDYLAY_DATA) {
|
|
if(session->callbacks.on_data_send_callback) {
|
|
session->callbacks.on_data_send_callback
|
|
(session,
|
|
frame->data.eof ? frame->data.flags :
|
|
(frame->data.flags & (~SPDYLAY_FLAG_FIN)),
|
|
frame->data.stream_id,
|
|
session->aob.framebuflen-SPDYLAY_HEAD_LEN, session->user_data);
|
|
}
|
|
} else {
|
|
if(session->callbacks.on_ctrl_send_callback) {
|
|
session->callbacks.on_ctrl_send_callback
|
|
(session, type, frame, session->user_data);
|
|
}
|
|
}
|
|
switch(type) {
|
|
case SPDYLAY_SYN_STREAM: {
|
|
spdylay_stream *stream =
|
|
spdylay_session_get_stream(session, frame->syn_stream.stream_id);
|
|
if(stream) {
|
|
spdylay_syn_stream_aux_data *aux_data;
|
|
stream->state = SPDYLAY_STREAM_OPENING;
|
|
if(frame->syn_stream.hd.flags & SPDYLAY_FLAG_FIN) {
|
|
spdylay_stream_shutdown(stream, SPDYLAY_SHUT_WR);
|
|
}
|
|
if(frame->syn_stream.hd.flags & SPDYLAY_FLAG_UNIDIRECTIONAL) {
|
|
spdylay_stream_shutdown(stream, SPDYLAY_SHUT_RD);
|
|
}
|
|
spdylay_session_close_stream_if_shut_rdwr(session, stream);
|
|
/* We assume aux_data is a pointer to spdylay_syn_stream_aux_data */
|
|
aux_data = (spdylay_syn_stream_aux_data*)item->aux_data;
|
|
if(aux_data->data_prd) {
|
|
int r;
|
|
/* spdylay_submit_data() makes a copy of aux_data->data_prd */
|
|
r = spdylay_submit_data(session, frame->syn_stream.stream_id,
|
|
SPDYLAY_FLAG_FIN, aux_data->data_prd);
|
|
if(r != 0) {
|
|
/* TODO If r is not FATAL, we should send RST_STREAM. */
|
|
return r;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SPDYLAY_SYN_REPLY: {
|
|
spdylay_stream *stream =
|
|
spdylay_session_get_stream(session, frame->syn_reply.stream_id);
|
|
if(stream) {
|
|
stream->state = SPDYLAY_STREAM_OPENED;
|
|
if(frame->syn_reply.hd.flags & SPDYLAY_FLAG_FIN) {
|
|
spdylay_stream_shutdown(stream, SPDYLAY_SHUT_WR);
|
|
}
|
|
spdylay_session_close_stream_if_shut_rdwr(session, stream);
|
|
if(item->aux_data) {
|
|
/* We assume aux_data is a pointer to spdylay_data_provider */
|
|
spdylay_data_provider *data_prd =
|
|
(spdylay_data_provider*)item->aux_data;
|
|
int r;
|
|
r = spdylay_submit_data(session, frame->syn_reply.stream_id,
|
|
SPDYLAY_FLAG_FIN, data_prd);
|
|
if(r != 0) {
|
|
/* TODO If r is not FATAL, we should send RST_STREAM. */
|
|
return r;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SPDYLAY_RST_STREAM:
|
|
if(!session->server &&
|
|
spdylay_session_is_my_stream_id(session, frame->rst_stream.stream_id) &&
|
|
frame->rst_stream.status_code == SPDYLAY_CANCEL) {
|
|
spdylay_session_close_pushed_streams(session, frame->rst_stream.stream_id,
|
|
frame->rst_stream.status_code);
|
|
}
|
|
spdylay_session_close_stream(session, frame->rst_stream.stream_id,
|
|
frame->rst_stream.status_code);
|
|
break;
|
|
case SPDYLAY_SETTINGS:
|
|
/* nothing to do */
|
|
break;
|
|
case SPDYLAY_NOOP:
|
|
/* We don't have any public API to add NOOP, so here is
|
|
unreachable. */
|
|
abort();
|
|
case SPDYLAY_PING:
|
|
/* We record the time now and show application code RTT when
|
|
reply PING is received. */
|
|
session->last_ping_unique_id = frame->ping.unique_id;
|
|
break;
|
|
case SPDYLAY_GOAWAY:
|
|
session->goaway_flags |= SPDYLAY_GOAWAY_SEND;
|
|
break;
|
|
case SPDYLAY_HEADERS:
|
|
/* Currently we don't have any API to send HEADERS frame, so this
|
|
is unreachable. */
|
|
abort();
|
|
case SPDYLAY_DATA:
|
|
if(frame->data.eof && (frame->data.flags & SPDYLAY_FLAG_FIN)) {
|
|
spdylay_stream *stream =
|
|
spdylay_session_get_stream(session, frame->data.stream_id);
|
|
if(stream) {
|
|
spdylay_stream_shutdown(stream, SPDYLAY_SHUT_WR);
|
|
spdylay_session_close_stream_if_shut_rdwr(session, stream);
|
|
}
|
|
}
|
|
break;
|
|
};
|
|
if(type == SPDYLAY_DATA) {
|
|
int r;
|
|
/* If session is closed or RST_STREAM was queued, we won't send
|
|
further data. */
|
|
if(frame->data.eof ||
|
|
!spdylay_session_is_data_allowed(session, frame->data.stream_id)) {
|
|
spdylay_active_outbound_item_reset(&session->aob);
|
|
} else {
|
|
spdylay_outbound_item* item = spdylay_session_get_next_ob_item(session);
|
|
if(item == NULL || session->aob.item->pri <= item->pri) {
|
|
/* 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. */
|
|
/* We assume that buffer has at least
|
|
SPDYLAY_DATA_FRAME_LENGTH. */
|
|
r = spdylay_session_pack_data_overwrite(session,
|
|
session->aob.framebuf,
|
|
SPDYLAY_DATA_FRAME_LENGTH,
|
|
&frame->data);
|
|
if(r < 0) {
|
|
spdylay_active_outbound_item_reset(&session->aob);
|
|
return r;
|
|
}
|
|
session->aob.framebuflen = r;
|
|
session->aob.framebufoff = 0;
|
|
} else {
|
|
r = spdylay_pq_push(&session->ob_pq, session->aob.item);
|
|
if(r == 0) {
|
|
session->aob.item = NULL;
|
|
spdylay_active_outbound_item_reset(&session->aob);
|
|
} else {
|
|
spdylay_active_outbound_item_reset(&session->aob);
|
|
return r;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
spdylay_active_outbound_item_reset(&session->aob);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int spdylay_session_send(spdylay_session *session)
|
|
{
|
|
int r;
|
|
while(1) {
|
|
const uint8_t *data;
|
|
size_t datalen;
|
|
ssize_t sentlen;
|
|
if(session->aob.item == NULL) {
|
|
spdylay_outbound_item *item;
|
|
ssize_t framebuflen;
|
|
item = spdylay_session_pop_next_ob_item(session);
|
|
if(item == NULL) {
|
|
break;
|
|
}
|
|
framebuflen = spdylay_session_prep_frame(session, item);
|
|
if(framebuflen < 0) {
|
|
/* TODO Call error callback? */
|
|
spdylay_outbound_item_free(item);
|
|
free(item);
|
|
if(framebuflen <= SPDYLAY_ERR_FATAL) {
|
|
return framebuflen;
|
|
} else {
|
|
continue;;
|
|
}
|
|
}
|
|
session->aob.item = item;
|
|
session->aob.framebuflen = framebuflen;
|
|
/* Call before_send callback */
|
|
if(item->frame_type != SPDYLAY_DATA &&
|
|
session->callbacks.before_ctrl_send_callback) {
|
|
session->callbacks.before_ctrl_send_callback
|
|
(session, item->frame_type, item->frame, 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) {
|
|
if(sentlen == SPDYLAY_ERR_WOULDBLOCK) {
|
|
return 0;
|
|
} else {
|
|
return sentlen;
|
|
}
|
|
} else {
|
|
session->aob.framebufoff += sentlen;
|
|
if(session->aob.framebufoff == session->aob.framebuflen) {
|
|
/* Frame has completely sent */
|
|
r = spdylay_session_after_frame_sent(session);
|
|
if(r < 0) {
|
|
return r;
|
|
}
|
|
} else {
|
|
/* partial write */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void spdylay_inbound_buffer_shift(spdylay_inbound_buffer *ibuf)
|
|
{
|
|
ptrdiff_t len = ibuf->limit-ibuf->mark;
|
|
memmove(ibuf->buf, ibuf->mark, len);
|
|
ibuf->limit = ibuf->buf+len;
|
|
ibuf->mark = ibuf->buf;
|
|
}
|
|
|
|
static ssize_t spdylay_recv(spdylay_session *session)
|
|
{
|
|
ssize_t r;
|
|
size_t recv_max;
|
|
if(session->ibuf.mark != session->ibuf.buf) {
|
|
spdylay_inbound_buffer_shift(&session->ibuf);
|
|
}
|
|
recv_max = session->ibuf.buf+sizeof(session->ibuf.buf)-session->ibuf.limit;
|
|
r = session->callbacks.recv_callback
|
|
(session, session->ibuf.limit, recv_max, 0, session->user_data);
|
|
if(r > 0) {
|
|
if(r > recv_max) {
|
|
return SPDYLAY_ERR_CALLBACK_FAILURE;
|
|
} else {
|
|
session->ibuf.limit += r;
|
|
}
|
|
} else if(r < 0) {
|
|
if(r != SPDYLAY_ERR_WOULDBLOCK) {
|
|
r = SPDYLAY_ERR_CALLBACK_FAILURE;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static size_t spdylay_inbound_buffer_avail(spdylay_inbound_buffer *ibuf)
|
|
{
|
|
return ibuf->limit-ibuf->mark;
|
|
}
|
|
|
|
static void spdylay_inbound_frame_reset(spdylay_inbound_frame *iframe)
|
|
{
|
|
iframe->state = SPDYLAY_RECV_HEAD;
|
|
iframe->len = iframe->off = 0;
|
|
iframe->ign = 0;
|
|
}
|
|
|
|
static void spdylay_session_call_on_request_recv
|
|
(spdylay_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);
|
|
}
|
|
}
|
|
|
|
static void spdylay_session_call_on_ctrl_frame_received
|
|
(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame)
|
|
{
|
|
if(session->callbacks.on_ctrl_recv_callback) {
|
|
session->callbacks.on_ctrl_recv_callback
|
|
(session, type, frame, session->user_data);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Checks whether received stream_id is valid.
|
|
* This function returns 1 if it succeeds, or 0.
|
|
*/
|
|
static int spdylay_session_is_new_peer_stream_id(spdylay_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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns non-zero iff version == SPDYLAY_PROTO_VERSION
|
|
*/
|
|
static int spdylay_session_check_version(uint16_t version)
|
|
{
|
|
return version == SPDYLAY_PROTO_VERSION;
|
|
}
|
|
|
|
/*
|
|
* Returns non-zero iff name/value pairs |nv| are good shape.
|
|
* Currently, we only checks whether names are lower cased. The spdy/2
|
|
* spec requires that names must be lower cased.
|
|
*/
|
|
static int spdylay_session_check_nv(char **nv)
|
|
{
|
|
int i;
|
|
for(i = 0; nv[i]; i += 2) {
|
|
int j;
|
|
for(j = 0; nv[i][j] != '\0'; ++j) {
|
|
if('A' <= nv[i][j] && nv[i][j] <= 'Z') {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Validates SYN_STREAM frame |frame|. This function returns 0 if it
|
|
* succeeds, or non-zero spdylay_status_code.
|
|
*/
|
|
static int spdylay_session_validate_syn_stream(spdylay_session *session,
|
|
spdylay_syn_stream *frame)
|
|
{
|
|
if(!spdylay_session_is_new_peer_stream_id(session, frame->stream_id)) {
|
|
return SPDYLAY_PROTOCOL_ERROR;
|
|
}
|
|
if(!spdylay_session_check_version(frame->hd.version)) {
|
|
return SPDYLAY_UNSUPPORTED_VERSION;
|
|
}
|
|
if(session->server) {
|
|
if(frame->assoc_stream_id != 0) {
|
|
return SPDYLAY_PROTOCOL_ERROR;
|
|
}
|
|
} else {
|
|
if(frame->assoc_stream_id == 0) {
|
|
/* spdy/2 spec: When a client receives a SYN_STREAM from the
|
|
server with an Associated-To-Stream-ID of 0, it must reply with
|
|
a RST_STREAM with error code INVALID_STREAM. */
|
|
return SPDYLAY_INVALID_STREAM;
|
|
}
|
|
if((frame->hd.flags & SPDYLAY_FLAG_UNIDIRECTIONAL) == 0 ||
|
|
frame->assoc_stream_id % 2 == 0 ||
|
|
spdylay_session_get_stream(session, frame->assoc_stream_id) == NULL) {
|
|
/* It seems spdy/2 spec does not say which status code should be
|
|
returned in these cases. */
|
|
return SPDYLAY_PROTOCOL_ERROR;
|
|
}
|
|
}
|
|
if(spdylay_session_get_max_concurrent_streams_reached(session)) {
|
|
/* spdy/2 spec does not clearly say what to do when max concurrent
|
|
streams number is reached. The mod_spdy sends
|
|
SPDYLAY_REFUSED_STREAM and we think it is reasonable. So we
|
|
follow it. */
|
|
return SPDYLAY_REFUSED_STREAM;
|
|
}
|
|
if(!spdylay_session_check_nv(frame->nv)) {
|
|
return SPDYLAY_PROTOCOL_ERROR;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int spdylay_session_handle_invalid_stream
|
|
(spdylay_session *session,
|
|
int32_t stream_id,
|
|
spdylay_frame_type type,
|
|
spdylay_frame *frame,
|
|
spdylay_status_code status_code)
|
|
{
|
|
int r;
|
|
r = spdylay_session_add_rst_stream(session, stream_id, status_code);
|
|
if(r != 0) {
|
|
return r;
|
|
}
|
|
if(session->callbacks.on_invalid_ctrl_recv_callback) {
|
|
session->callbacks.on_invalid_ctrl_recv_callback
|
|
(session, type, frame, session->user_data);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int spdylay_session_on_syn_stream_received(spdylay_session *session,
|
|
spdylay_frame *frame)
|
|
{
|
|
/* TODO Check SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS */
|
|
int r = 0;
|
|
int status_code;
|
|
if(session->goaway_flags) {
|
|
/* We don't accept SYN_STREAM after GOAWAY is sent or received. */
|
|
return 0;
|
|
}
|
|
status_code = spdylay_session_validate_syn_stream(session,
|
|
&frame->syn_stream);
|
|
if(status_code == 0) {
|
|
uint8_t flags = frame->syn_stream.hd.flags;
|
|
if((flags & SPDYLAY_FLAG_FIN) && (flags & SPDYLAY_FLAG_UNIDIRECTIONAL)) {
|
|
/* If the stream is UNIDIRECTIONAL and FIN bit set, we can close
|
|
stream upon receiving SYN_STREAM. So, the stream needs not to
|
|
be opened. */
|
|
} else {
|
|
spdylay_stream *stream;
|
|
stream = spdylay_session_open_stream(session, frame->syn_stream.stream_id,
|
|
frame->syn_stream.hd.flags,
|
|
frame->syn_stream.pri,
|
|
SPDYLAY_STREAM_OPENING,
|
|
NULL);
|
|
if(stream) {
|
|
if(flags & SPDYLAY_FLAG_FIN) {
|
|
spdylay_stream_shutdown(stream, SPDYLAY_SHUT_RD);
|
|
}
|
|
if(flags & SPDYLAY_FLAG_UNIDIRECTIONAL) {
|
|
spdylay_stream_shutdown(stream, SPDYLAY_SHUT_WR);
|
|
}
|
|
/* We don't call spdylay_session_close_stream_if_shut_rdwr()
|
|
here because either SPDYLAY_FLAG_FIN or
|
|
SPDYLAY_FLAG_UNIDIRECTIONAL is not set here. */
|
|
}
|
|
}
|
|
session->last_recv_stream_id = frame->syn_stream.stream_id;
|
|
spdylay_session_call_on_ctrl_frame_received(session, SPDYLAY_SYN_STREAM,
|
|
frame);
|
|
if(flags & SPDYLAY_FLAG_FIN) {
|
|
spdylay_session_call_on_request_recv(session,
|
|
frame->syn_stream.stream_id);
|
|
if(flags & SPDYLAY_FLAG_UNIDIRECTIONAL) {
|
|
/* Note that we call on_stream_close_callback without opening
|
|
stream. */
|
|
if(session->callbacks.on_stream_close_callback) {
|
|
session->callbacks.on_stream_close_callback
|
|
(session, frame->syn_stream.stream_id, SPDYLAY_OK,
|
|
session->user_data);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
r = spdylay_session_handle_invalid_stream
|
|
(session, frame->syn_stream.stream_id, SPDYLAY_SYN_STREAM, frame,
|
|
status_code);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int spdylay_session_on_syn_reply_received(spdylay_session *session,
|
|
spdylay_frame *frame)
|
|
{
|
|
int r = 0;
|
|
int valid = 0;
|
|
spdylay_stream *stream;
|
|
if(!spdylay_session_check_version(frame->syn_reply.hd.version)) {
|
|
return 0;
|
|
}
|
|
if((stream = spdylay_session_get_stream(session,
|
|
frame->syn_reply.stream_id)) &&
|
|
(stream->shut_flags & SPDYLAY_SHUT_RD) == 0 &&
|
|
spdylay_session_check_nv(frame->syn_reply.nv)) {
|
|
if(spdylay_session_is_my_stream_id(session, frame->syn_reply.stream_id)) {
|
|
if(stream->state == SPDYLAY_STREAM_OPENING) {
|
|
valid = 1;
|
|
stream->state = SPDYLAY_STREAM_OPENED;
|
|
spdylay_session_call_on_ctrl_frame_received(session, SPDYLAY_SYN_REPLY,
|
|
frame);
|
|
if(frame->syn_reply.hd.flags & SPDYLAY_FLAG_FIN) {
|
|
/* This is the last frame of this stream, so disallow
|
|
further receptions. */
|
|
spdylay_stream_shutdown(stream, SPDYLAY_SHUT_RD);
|
|
spdylay_session_close_stream_if_shut_rdwr(session, stream);
|
|
}
|
|
} else if(stream->state == SPDYLAY_STREAM_CLOSING) {
|
|
/* This is race condition. SPDYLAY_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;
|
|
}
|
|
}
|
|
}
|
|
if(!valid) {
|
|
r = spdylay_session_handle_invalid_stream
|
|
(session, frame->syn_reply.stream_id, SPDYLAY_SYN_REPLY, frame,
|
|
SPDYLAY_PROTOCOL_ERROR);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int spdylay_session_on_rst_stream_received(spdylay_session *session,
|
|
spdylay_frame *frame)
|
|
{
|
|
if(!spdylay_session_check_version(frame->rst_stream.hd.version)) {
|
|
return 0;
|
|
}
|
|
if(session->server &&
|
|
!spdylay_session_is_my_stream_id(session, frame->rst_stream.stream_id) &&
|
|
frame->rst_stream.status_code == SPDYLAY_CANCEL) {
|
|
spdylay_session_close_pushed_streams(session, frame->rst_stream.stream_id,
|
|
frame->rst_stream.status_code);
|
|
}
|
|
spdylay_session_close_stream(session, frame->rst_stream.stream_id,
|
|
frame->rst_stream.status_code);
|
|
return 0;
|
|
}
|
|
|
|
int spdylay_session_on_settings_received(spdylay_session *session,
|
|
spdylay_frame *frame)
|
|
{
|
|
if(!spdylay_session_check_version(frame->settings.hd.version)) {
|
|
return 0;
|
|
}
|
|
/* TODO Check ID/value pairs and persist them if necessary. */
|
|
spdylay_session_call_on_ctrl_frame_received(session, SPDYLAY_SETTINGS, frame);
|
|
return 0;
|
|
}
|
|
|
|
int spdylay_session_on_ping_received(spdylay_session *session,
|
|
spdylay_frame *frame)
|
|
{
|
|
int r = 0;
|
|
if(!spdylay_session_check_version(frame->ping.hd.version)) {
|
|
return 0;
|
|
}
|
|
if(frame->ping.unique_id != 0) {
|
|
if(session->last_ping_unique_id == frame->ping.unique_id) {
|
|
/* This is ping reply from peer */
|
|
/* Assign 0 to last_ping_unique_id so that we can ignore same
|
|
ID. */
|
|
session->last_ping_unique_id = 0;
|
|
spdylay_session_call_on_ctrl_frame_received(session, SPDYLAY_PING, frame);
|
|
} else if((session->server && frame->ping.unique_id % 2 == 1) ||
|
|
(!session->server && frame->ping.unique_id % 2 == 0)) {
|
|
/* Peer sent ping, so ping it back */
|
|
r = spdylay_session_add_ping(session, frame->ping.unique_id);
|
|
spdylay_session_call_on_ctrl_frame_received(session, SPDYLAY_PING, frame);
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int spdylay_session_on_goaway_received(spdylay_session *session,
|
|
spdylay_frame *frame)
|
|
{
|
|
if(!spdylay_session_check_version(frame->goaway.hd.version)) {
|
|
return 0;
|
|
}
|
|
session->last_good_stream_id = frame->goaway.last_good_stream_id;
|
|
session->goaway_flags |= SPDYLAY_GOAWAY_RECV;
|
|
spdylay_session_call_on_ctrl_frame_received(session, SPDYLAY_GOAWAY, frame);
|
|
return 0;
|
|
}
|
|
|
|
int spdylay_session_on_headers_received(spdylay_session *session,
|
|
spdylay_frame *frame)
|
|
{
|
|
int r = 0;
|
|
int valid = 0;
|
|
spdylay_stream *stream;
|
|
if(!spdylay_session_check_version(frame->headers.hd.version)) {
|
|
return 0;
|
|
}
|
|
if((stream = spdylay_session_get_stream(session,
|
|
frame->headers.stream_id)) &&
|
|
(stream->shut_flags & SPDYLAY_SHUT_RD) == 0 &&
|
|
spdylay_session_check_nv(frame->headers.nv)) {
|
|
if(spdylay_session_is_my_stream_id(session, frame->headers.stream_id)) {
|
|
if(stream->state == SPDYLAY_STREAM_OPENED) {
|
|
valid = 1;
|
|
spdylay_session_call_on_ctrl_frame_received(session, SPDYLAY_HEADERS,
|
|
frame);
|
|
if(frame->headers.hd.flags & SPDYLAY_FLAG_FIN) {
|
|
spdylay_stream_shutdown(stream, SPDYLAY_SHUT_RD);
|
|
spdylay_session_close_stream_if_shut_rdwr(session, stream);
|
|
}
|
|
} else if(stream->state == SPDYLAY_STREAM_CLOSING) {
|
|
/* This is race condition. SPDYLAY_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
|
|
have sent FIN frame already. But if stream is in
|
|
SPDYLAY_STREAM_CLOSING, we discard the frame. This is a race
|
|
condition. */
|
|
valid = 1;
|
|
if(stream->state != SPDYLAY_STREAM_CLOSING) {
|
|
spdylay_session_call_on_ctrl_frame_received(session, SPDYLAY_HEADERS,
|
|
frame);
|
|
if(frame->headers.hd.flags & SPDYLAY_FLAG_FIN) {
|
|
spdylay_session_call_on_request_recv(session,
|
|
frame->headers.stream_id);
|
|
spdylay_stream_shutdown(stream, SPDYLAY_SHUT_RD);
|
|
spdylay_session_close_stream_if_shut_rdwr(session, stream);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(!valid) {
|
|
r = spdylay_session_handle_invalid_stream
|
|
(session, frame->headers.stream_id, SPDYLAY_HEADERS, frame,
|
|
SPDYLAY_PROTOCOL_ERROR);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static int spdylay_session_process_ctrl_frame(spdylay_session *session)
|
|
{
|
|
int r = 0;
|
|
uint16_t type;
|
|
spdylay_frame frame;
|
|
memcpy(&type, &session->iframe.headbuf[2], sizeof(uint16_t));
|
|
type = ntohs(type);
|
|
switch(type) {
|
|
case SPDYLAY_SYN_STREAM:
|
|
spdylay_buffer_reset(&session->inflatebuf);
|
|
r = spdylay_frame_unpack_syn_stream(&frame.syn_stream,
|
|
&session->inflatebuf,
|
|
&session->nvbuf,
|
|
&session->nvbuflen,
|
|
session->iframe.headbuf,
|
|
sizeof(session->iframe.headbuf),
|
|
session->iframe.buf,
|
|
session->iframe.len,
|
|
&session->hd_inflater);
|
|
if(r == 0) {
|
|
r = spdylay_session_on_syn_stream_received(session, &frame);
|
|
spdylay_frame_syn_stream_free(&frame.syn_stream);
|
|
} else {
|
|
/* TODO if r indicates mulformed NV pairs (multiple nulls) or
|
|
invalid frame, send RST_STREAM with PROTOCOL_ERROR. Same for
|
|
other control frames. */
|
|
}
|
|
break;
|
|
case SPDYLAY_SYN_REPLY:
|
|
spdylay_buffer_reset(&session->inflatebuf);
|
|
r = spdylay_frame_unpack_syn_reply(&frame.syn_reply,
|
|
&session->inflatebuf,
|
|
&session->nvbuf,
|
|
&session->nvbuflen,
|
|
session->iframe.headbuf,
|
|
sizeof(session->iframe.headbuf),
|
|
session->iframe.buf,
|
|
session->iframe.len,
|
|
&session->hd_inflater);
|
|
if(r == 0) {
|
|
r = spdylay_session_on_syn_reply_received(session, &frame);
|
|
spdylay_frame_syn_reply_free(&frame.syn_reply);
|
|
}
|
|
break;
|
|
case SPDYLAY_RST_STREAM:
|
|
r = spdylay_frame_unpack_rst_stream(&frame.rst_stream,
|
|
session->iframe.headbuf,
|
|
sizeof(session->iframe.headbuf),
|
|
session->iframe.buf,
|
|
session->iframe.len);
|
|
if(r == 0) {
|
|
r = spdylay_session_on_rst_stream_received(session, &frame);
|
|
spdylay_frame_rst_stream_free(&frame.rst_stream);
|
|
}
|
|
break;
|
|
case SPDYLAY_SETTINGS:
|
|
r = spdylay_frame_unpack_settings(&frame.settings,
|
|
session->iframe.headbuf,
|
|
sizeof(session->iframe.headbuf),
|
|
session->iframe.buf,
|
|
session->iframe.len);
|
|
if(r == 0) {
|
|
r = spdylay_session_on_settings_received(session, &frame);
|
|
spdylay_frame_settings_free(&frame.settings);
|
|
}
|
|
break;
|
|
case SPDYLAY_NOOP:
|
|
break;
|
|
case SPDYLAY_PING:
|
|
r = spdylay_frame_unpack_ping(&frame.ping,
|
|
session->iframe.headbuf,
|
|
sizeof(session->iframe.headbuf),
|
|
session->iframe.buf,
|
|
session->iframe.len);
|
|
if(r == 0) {
|
|
r = spdylay_session_on_ping_received(session, &frame);
|
|
spdylay_frame_ping_free(&frame.ping);
|
|
}
|
|
break;
|
|
case SPDYLAY_GOAWAY:
|
|
r = spdylay_frame_unpack_goaway(&frame.goaway,
|
|
session->iframe.headbuf,
|
|
sizeof(session->iframe.headbuf),
|
|
session->iframe.buf,
|
|
session->iframe.len);
|
|
if(r == 0) {
|
|
r = spdylay_session_on_goaway_received(session, &frame);
|
|
spdylay_frame_goaway_free(&frame.goaway);
|
|
}
|
|
break;
|
|
case SPDYLAY_HEADERS:
|
|
spdylay_buffer_reset(&session->inflatebuf);
|
|
r = spdylay_frame_unpack_headers(&frame.headers,
|
|
&session->inflatebuf,
|
|
&session->nvbuf,
|
|
&session->nvbuflen,
|
|
session->iframe.headbuf,
|
|
sizeof(session->iframe.headbuf),
|
|
session->iframe.buf,
|
|
session->iframe.len,
|
|
&session->hd_inflater);
|
|
if(r == 0) {
|
|
r = spdylay_session_on_headers_received(session, &frame);
|
|
spdylay_frame_headers_free(&frame.headers);
|
|
}
|
|
break;
|
|
}
|
|
if(r < SPDYLAY_ERR_FATAL) {
|
|
return r;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int spdylay_session_on_data_received(spdylay_session *session,
|
|
uint8_t flags, int32_t length,
|
|
int32_t stream_id)
|
|
{
|
|
int r = 0;
|
|
spdylay_status_code status_code = 0;
|
|
spdylay_stream *stream;
|
|
stream = spdylay_session_get_stream(session, stream_id);
|
|
if(stream) {
|
|
if((stream->shut_flags & SPDYLAY_SHUT_RD) == 0) {
|
|
int valid = 0;
|
|
if(spdylay_session_is_my_stream_id(session, stream_id)) {
|
|
if(stream->state == SPDYLAY_STREAM_OPENED) {
|
|
valid = 1;
|
|
if(session->callbacks.on_data_recv_callback) {
|
|
session->callbacks.on_data_recv_callback
|
|
(session, flags, stream_id, length, session->user_data);
|
|
}
|
|
} else if(stream->state != SPDYLAY_STREAM_CLOSING) {
|
|
status_code = SPDYLAY_PROTOCOL_ERROR;
|
|
}
|
|
} else if(stream->state != SPDYLAY_STREAM_CLOSING) {
|
|
/* It is OK if this is remote peer initiated stream and we did
|
|
not receive FIN unless stream is in SPDYLAY_STREAM_CLOSING
|
|
state. This is a race condition. */
|
|
valid = 1;
|
|
if(session->callbacks.on_data_recv_callback) {
|
|
session->callbacks.on_data_recv_callback
|
|
(session, flags, stream_id, length, session->user_data);
|
|
}
|
|
if(flags & SPDYLAY_FLAG_FIN) {
|
|
spdylay_session_call_on_request_recv(session, stream_id);
|
|
}
|
|
}
|
|
if(valid) {
|
|
if(flags & SPDYLAY_FLAG_FIN) {
|
|
spdylay_stream_shutdown(stream, SPDYLAY_SHUT_RD);
|
|
spdylay_session_close_stream_if_shut_rdwr(session, stream);
|
|
}
|
|
}
|
|
} else {
|
|
status_code = SPDYLAY_PROTOCOL_ERROR;
|
|
}
|
|
} else {
|
|
status_code = SPDYLAY_INVALID_STREAM;
|
|
}
|
|
if(status_code != 0) {
|
|
r = spdylay_session_add_rst_stream(session, stream_id, status_code);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static int spdylay_session_process_data_frame(spdylay_session *session)
|
|
{
|
|
uint8_t flags;
|
|
int32_t length;
|
|
int32_t stream_id;
|
|
int r;
|
|
stream_id = spdylay_get_uint32(session->iframe.headbuf) &
|
|
SPDYLAY_STREAM_ID_MASK;
|
|
flags = session->iframe.headbuf[4];
|
|
length = spdylay_get_uint32(&session->iframe.headbuf[4]) &
|
|
SPDYLAY_LENGTH_MASK;
|
|
r = spdylay_session_on_data_received(session, flags, length, stream_id);
|
|
if(r < SPDYLAY_ERR_FATAL) {
|
|
return r;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int spdylay_session_recv(spdylay_session *session)
|
|
{
|
|
while(1) {
|
|
ssize_t r;
|
|
if(session->iframe.state == SPDYLAY_RECV_HEAD) {
|
|
uint32_t payloadlen;
|
|
if(spdylay_inbound_buffer_avail(&session->ibuf) < SPDYLAY_HEAD_LEN) {
|
|
r = spdylay_recv(session);
|
|
/* If EOF is reached, r == SPDYLAY_ERR_EOF */
|
|
if(r < 0) {
|
|
if(r == SPDYLAY_ERR_WOULDBLOCK) {
|
|
return 0;
|
|
} else {
|
|
return r;
|
|
}
|
|
}
|
|
if(spdylay_inbound_buffer_avail(&session->ibuf) < SPDYLAY_HEAD_LEN) {
|
|
return 0;
|
|
}
|
|
}
|
|
session->iframe.state = SPDYLAY_RECV_PAYLOAD;
|
|
payloadlen = spdylay_get_uint32(&session->ibuf.mark[4]) &
|
|
SPDYLAY_LENGTH_MASK;
|
|
memcpy(session->iframe.headbuf, session->ibuf.mark, SPDYLAY_HEAD_LEN);
|
|
session->ibuf.mark += SPDYLAY_HEAD_LEN;
|
|
if(spdylay_frame_is_ctrl_frame(session->iframe.headbuf[0])) {
|
|
/* control frame */
|
|
session->iframe.len = payloadlen;
|
|
r = spdylay_reserve_buffer(&session->iframe.buf,
|
|
&session->iframe.bufmax,
|
|
session->iframe.len);
|
|
if(r != 0) {
|
|
return r;
|
|
}
|
|
session->iframe.off = 0;
|
|
} else {
|
|
/* TODO validate stream id here */
|
|
session->iframe.len = payloadlen;
|
|
session->iframe.off = 0;
|
|
}
|
|
}
|
|
if(session->iframe.state == SPDYLAY_RECV_PAYLOAD) {
|
|
size_t rempayloadlen = session->iframe.len - session->iframe.off;
|
|
size_t bufavail, readlen;
|
|
if(spdylay_inbound_buffer_avail(&session->ibuf) == 0 &&
|
|
rempayloadlen > 0) {
|
|
r = spdylay_recv(session);
|
|
if(r == 0 || r == SPDYLAY_ERR_WOULDBLOCK) {
|
|
return 0;
|
|
} else if(r < 0) {
|
|
return r;
|
|
}
|
|
}
|
|
bufavail = spdylay_inbound_buffer_avail(&session->ibuf);
|
|
readlen = bufavail < rempayloadlen ? bufavail : rempayloadlen;
|
|
if(spdylay_frame_is_ctrl_frame(session->iframe.headbuf[0])) {
|
|
memcpy(session->iframe.buf, session->ibuf.mark, readlen);
|
|
} else if(session->callbacks.on_data_chunk_recv_callback) {
|
|
int32_t stream_id;
|
|
uint8_t flags;
|
|
/* For data frame, We don't buffer data. Instead, just pass
|
|
received data to callback function. */
|
|
stream_id = spdylay_get_uint32(session->iframe.headbuf) &
|
|
SPDYLAY_STREAM_ID_MASK;
|
|
flags = session->iframe.headbuf[4];
|
|
session->callbacks.on_data_chunk_recv_callback(session,
|
|
flags,
|
|
stream_id,
|
|
session->ibuf.mark,
|
|
readlen,
|
|
session->user_data);
|
|
}
|
|
session->iframe.off += readlen;
|
|
session->ibuf.mark += readlen;
|
|
if(session->iframe.len == session->iframe.off) {
|
|
if(spdylay_frame_is_ctrl_frame(session->iframe.headbuf[0])) {
|
|
r = spdylay_session_process_ctrl_frame(session);
|
|
} else {
|
|
r = spdylay_session_process_data_frame(session);
|
|
}
|
|
if(r < 0) {
|
|
/* Fatal error */
|
|
return r;
|
|
}
|
|
spdylay_inbound_frame_reset(&session->iframe);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int spdylay_session_want_read(spdylay_session *session)
|
|
{
|
|
/* Unless GOAWAY is sent or received, we always want to read
|
|
incoming frames. After GOAWAY is sent or received, we are only
|
|
interested in active streams. */
|
|
return !session->goaway_flags || spdylay_map_size(&session->streams) > 0;
|
|
}
|
|
|
|
int spdylay_session_want_write(spdylay_session *session)
|
|
{
|
|
/*
|
|
* Unless GOAWAY is sent or received, we want to write frames if
|
|
* there is pending ones. If pending frame is SYN_STREAM and
|
|
* concurrent stream limit is reached, we don't want to write
|
|
* SYN_STREAM. After GOAWAY is sent or received, we want to write
|
|
* frames if there is pending ones AND there are active frames.
|
|
*/
|
|
return (session->aob.item != NULL || !spdylay_pq_empty(&session->ob_pq) ||
|
|
(!spdylay_pq_empty(&session->ob_ss_pq) &&
|
|
!spdylay_session_get_max_concurrent_streams_reached(session))) &&
|
|
(!session->goaway_flags || spdylay_map_size(&session->streams) > 0);
|
|
}
|
|
|
|
int spdylay_session_add_ping(spdylay_session *session, uint32_t unique_id)
|
|
{
|
|
int r;
|
|
spdylay_frame *frame;
|
|
frame = malloc(sizeof(spdylay_frame));
|
|
if(frame == NULL) {
|
|
return SPDYLAY_ERR_NOMEM;
|
|
}
|
|
spdylay_frame_ping_init(&frame->ping, unique_id);
|
|
r = spdylay_session_add_frame(session, SPDYLAY_PING, frame, NULL);
|
|
if(r != 0) {
|
|
spdylay_frame_ping_free(&frame->ping);
|
|
free(frame);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int spdylay_session_add_goaway(spdylay_session *session,
|
|
int32_t last_good_stream_id)
|
|
{
|
|
int r;
|
|
spdylay_frame *frame;
|
|
frame = malloc(sizeof(spdylay_frame));
|
|
if(frame == NULL) {
|
|
return SPDYLAY_ERR_NOMEM;
|
|
}
|
|
spdylay_frame_goaway_init(&frame->goaway, last_good_stream_id);
|
|
r = spdylay_session_add_frame(session, SPDYLAY_GOAWAY, frame, NULL);
|
|
if(r != 0) {
|
|
spdylay_frame_goaway_free(&frame->goaway);
|
|
free(frame);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
ssize_t spdylay_session_pack_data(spdylay_session *session,
|
|
uint8_t **buf_ptr, size_t *buflen_ptr,
|
|
spdylay_data *frame)
|
|
{
|
|
ssize_t framelen = SPDYLAY_DATA_FRAME_LENGTH;
|
|
int r;
|
|
r = spdylay_reserve_buffer(buf_ptr, buflen_ptr, framelen);
|
|
if(r != 0) {
|
|
return r;
|
|
}
|
|
framelen = spdylay_session_pack_data_overwrite(session, *buf_ptr, framelen,
|
|
frame);
|
|
return framelen;
|
|
}
|
|
|
|
ssize_t spdylay_session_pack_data_overwrite(spdylay_session *session,
|
|
uint8_t *buf, size_t len,
|
|
spdylay_data *frame)
|
|
{
|
|
ssize_t r;
|
|
int eof = 0;
|
|
uint8_t flags = 0;
|
|
r = frame->data_prd.read_callback
|
|
(session, buf+8, len-8, &eof, &frame->data_prd.source, session->user_data);
|
|
if(r < 0) {
|
|
return r;
|
|
} else if(len < r) {
|
|
return SPDYLAY_ERR_CALLBACK_FAILURE;
|
|
}
|
|
memset(buf, 0, SPDYLAY_HEAD_LEN);
|
|
spdylay_put_uint32be(&buf[0], frame->stream_id);
|
|
spdylay_put_uint32be(&buf[4], r);
|
|
if(eof) {
|
|
frame->eof = 1;
|
|
if(frame->flags & SPDYLAY_FLAG_FIN) {
|
|
flags |= SPDYLAY_FLAG_FIN;
|
|
}
|
|
}
|
|
buf[4] = flags;
|
|
return r+8;
|
|
}
|
|
|
|
uint32_t spdylay_session_get_next_unique_id(spdylay_session *session)
|
|
{
|
|
uint32_t ret_id;
|
|
if(session->next_unique_id > SPDYLAY_MAX_UNIQUE_ID) {
|
|
if(session->server) {
|
|
session->next_unique_id = 2;
|
|
} else {
|
|
session->next_unique_id = 1;
|
|
}
|
|
}
|
|
ret_id = session->next_unique_id;
|
|
session->next_unique_id += 2;
|
|
return ret_id;
|
|
}
|
|
|
|
void* spdylay_session_get_stream_user_data(spdylay_session *session,
|
|
int32_t stream_id)
|
|
{
|
|
spdylay_stream *stream;
|
|
stream = spdylay_session_get_stream(session, stream_id);
|
|
if(stream) {
|
|
return stream->stream_user_data;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|