/* * 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 #include #include #include #include #include "spdylay_stream.h" #include "spdylay_helper.h" /* * Returns spdylay_stream* object whose stream ID is |stream_id|. It * could be NULL if such stream does not exist. */ static spdylay_stream* spdylay_session_get_stream(spdylay_session *session, int32_t stream_id) { return (spdylay_stream*)spdylay_map_find(&session->streams, stream_id); } 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; return lhs->pri-rhs->pri; } int spdylay_session_client_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) { return SPDYLAY_ERR_NOMEM; } memset(*session_ptr, 0, sizeof(spdylay_session)); (*session_ptr)->next_stream_id = 1; (*session_ptr)->last_recv_stream_id = 0; r = spdylay_zlib_deflate_hd_init(&(*session_ptr)->hd_deflater); if(r != 0) { free(*session_ptr); return r; } r = spdylay_zlib_inflate_hd_init(&(*session_ptr)->hd_inflater); if(r != 0) { spdylay_zlib_deflate_free(&(*session_ptr)->hd_deflater); free(*session_ptr); return r; } r = spdylay_map_init(&(*session_ptr)->streams); if(r != 0) { spdylay_zlib_inflate_free(&(*session_ptr)->hd_inflater); spdylay_zlib_deflate_free(&(*session_ptr)->hd_deflater); free(*session_ptr); return r; } r = spdylay_pq_init(&(*session_ptr)->ob_pq, spdylay_outbound_item_compar); if(r != 0) { spdylay_map_free(&(*session_ptr)->streams); spdylay_zlib_inflate_free(&(*session_ptr)->hd_inflater); spdylay_zlib_deflate_free(&(*session_ptr)->hd_deflater); free(*session_ptr); return r; } (*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; return 0; } static void spdylay_free_streams(key_type key, void *val) { spdylay_stream_free((spdylay_stream*)val); free(val); } static 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); break; } free(item->frame); } void spdylay_session_del(spdylay_session *session) { spdylay_map_each(&session->streams, spdylay_free_streams); spdylay_map_free(&session->streams); while(!spdylay_pq_empty(&session->ob_pq)) { spdylay_outbound_item *item = (spdylay_outbound_item*) spdylay_pq_top(&session->ob_pq); spdylay_outbound_item_free(item); free(item); spdylay_pq_pop(&session->ob_pq); } spdylay_pq_free(&session->ob_pq); spdylay_zlib_deflate_free(&session->hd_deflater); spdylay_zlib_inflate_free(&session->hd_inflater); free(session->iframe.buf); free(session); } int spdylay_session_add_frame(spdylay_session *session, spdylay_frame_type frame_type, spdylay_frame *frame) { 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; /* TODO Add pri field to SYN_REPLY, DATA frame which copies corresponding SYN_STREAM pri. PING frame always pri = 0 (highest) */ switch(frame_type) { case SPDYLAY_SYN_STREAM: item->pri = 4-frame->syn_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; } else { item->pri = 4; } break; } default: item->pri = 4; }; 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); if(r != 0) { spdylay_frame_rst_stream_free(&frame->rst_stream); free(frame); return r; } return 0; } int spdylay_session_open_stream(spdylay_session *session, int32_t stream_id, uint8_t flags, uint8_t pri) { int r; spdylay_stream *stream = malloc(sizeof(spdylay_stream)); if(stream == NULL) { return SPDYLAY_ERR_NOMEM; } spdylay_stream_init(stream, stream_id, flags, pri); r = spdylay_map_insert(&session->streams, stream_id, stream); if(r != 0) { free(stream); } return r; } ssize_t spdylay_session_prep_frame(spdylay_session *session, spdylay_outbound_item *item, uint8_t **framebuf_ptr) { uint8_t *framebuf; ssize_t framebuflen; int r; switch(item->frame_type) { case SPDYLAY_SYN_STREAM: { item->frame->syn_stream.stream_id = session->next_stream_id; session->next_stream_id += 2; framebuflen = spdylay_frame_pack_syn_stream(&framebuf, &item->frame->syn_stream, &session->hd_deflater); if(framebuflen < 0) { return framebuflen; } printf("packed %d bytes\n", framebuflen); r = spdylay_session_open_stream(session, item->frame->syn_stream.stream_id, item->frame->syn_stream.hd.flags, item->frame->syn_stream.pri); if(r != 0) { free(framebuf); return r; } *framebuf_ptr = framebuf; 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); free(aob->framebuf); memset(aob, 0, sizeof(spdylay_active_outbound_item)); } int spdylay_session_send(spdylay_session *session) { printf("session_send\n"); while(session->aob.item || !spdylay_pq_empty(&session->ob_pq)) { const uint8_t *data; size_t datalen; ssize_t sentlen; if(session->aob.item == NULL) { spdylay_outbound_item *item = spdylay_pq_top(&session->ob_pq); uint8_t *framebuf; ssize_t framebuflen; spdylay_pq_pop(&session->ob_pq); /* TODO Get or validate stream id here */ framebuflen = spdylay_session_prep_frame(session, item, &framebuf); if(framebuflen < 0) { /* TODO Call error callback? */ spdylay_outbound_item_free(item); free(item); continue;; } session->aob.item = item; session->aob.framebuf = framebuf; session->aob.framebuflen = framebuflen; } 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 { printf("sent %d bytes\n", sentlen); session->aob.framebufoff += sentlen; if(session->aob.framebufoff == session->aob.framebuflen) { /* Frame has completely sent */ spdylay_active_outbound_item_reset(&session->aob); /* TODO If frame is data frame, we need to sent all chunk of data.*/ } 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; free(iframe->buf); iframe->buf = NULL; iframe->len = iframe->off = 0; iframe->ign = 0; } static void spdylay_debug_print_nv(char **nv) { int i; for(i = 0; nv[i]; i += 2) { printf("%s: %s\n", nv[i], nv[i+1]); } } 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; } } static 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) || r == 1; } /* * Validates SYN_STREAM frame |frame|. This function returns 0 if it * succeeds, or -1. */ static int spdylay_session_validate_syn_stream(spdylay_session *session, spdylay_syn_stream *frame) { /* TODO Check assoc_stream_id */ if(spdylay_session_is_new_peer_stream_id(session, frame->stream_id)) { return 0; } else { return -1; } } static int spdylay_session_handle_invalid_ctrl_frame(spdylay_session *session, int32_t stream_id, spdylay_frame_type type, spdylay_frame *frame) { int r; r = spdylay_session_add_rst_stream(session, stream_id, SPDYLAY_PROTOCOL_ERROR); 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) { int r; if(spdylay_session_validate_syn_stream(session, &frame->syn_stream) == 0) { r = spdylay_session_open_stream(session, frame->syn_stream.stream_id, frame->syn_stream.hd.flags, frame->syn_stream.pri); if(r == 0) { session->last_recv_stream_id = frame->syn_stream.stream_id; spdylay_session_call_on_ctrl_frame_received(session, SPDYLAY_SYN_STREAM, frame); } } else { r = spdylay_session_handle_invalid_ctrl_frame (session, frame->syn_stream.stream_id, SPDYLAY_SYN_STREAM, frame); } return r; } int spdylay_session_on_syn_reply_received(spdylay_session *session, spdylay_frame *frame) { int r = 0; int valid = 0; if(spdylay_session_is_my_stream_id(session, frame->syn_reply.stream_id)) { spdylay_stream *stream = spdylay_session_get_stream (session, frame->syn_reply.stream_id); if(stream) { 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); } 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_ctrl_frame (session, frame->syn_reply.stream_id, SPDYLAY_SYN_REPLY, frame); } return r; } 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: printf("SYN_STREAM\n"); r = spdylay_frame_unpack_syn_stream(&frame.syn_stream, 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: printf("SYN_REPLY\n"); r = spdylay_frame_unpack_syn_reply(&frame.syn_reply, 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; default: /* ignore */ printf("Received control frame type %x\n", type); } if(r < SPDYLAY_ERR_FATAL) { return r; } else { return 0; } } int spdylay_session_process_data_frame(spdylay_session *session) { printf("Received data frame, stream_id %d, %zu bytes, fin=%d\n", spdylay_get_uint32(session->iframe.headbuf), session->iframe.len, session->iframe.headbuf[4] & SPDYLAY_FLAG_FIN); return 0; } int spdylay_session_recv(spdylay_session *session) { printf("session_recv\n"); 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); printf("r=%d\n", r); /* TODO handle EOF */ if(r < 0) { if(r == SPDYLAY_ERR_WOULDBLOCK) { return 0; } else { return r; } } printf("Recved %d bytes\n", 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]) & 0xffffff; 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; session->iframe.buf = malloc(session->iframe.len); if(session->iframe.buf == NULL) { return SPDYLAY_ERR_NOMEM; } session->iframe.off = 0; } else { int32_t stream_id; /* data frame */ /* For data frame, We dont' buffer data. Instead, just pass received data to callback function. */ stream_id = spdylay_get_uint32(session->iframe.headbuf) & 0x7fffffff; /* 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) { if(r == SPDYLAY_ERR_WOULDBLOCK) { return 0; } else { return r; } } } bufavail = spdylay_inbound_buffer_avail(&session->ibuf); readlen = bufavail < rempayloadlen ? bufavail : rempayloadlen; if(session->iframe.buf != NULL) { memcpy(session->iframe.buf, session->ibuf.mark, readlen); } 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); if(r < 0) { /* Fatal error */ return r; } } else { spdylay_session_process_data_frame(session); } spdylay_inbound_frame_reset(&session->iframe); } } } return 0; } int spdylay_session_want_read(spdylay_session *session) { return 1; } int spdylay_session_want_write(spdylay_session *session) { return session->aob.item != NULL || !spdylay_pq_empty(&session->ob_pq); } int spdylay_req_submit(spdylay_session *session, const char *path) { int r; spdylay_frame *frame; char **nv; frame = malloc(sizeof(spdylay_frame)); nv = malloc(9*sizeof(char*)); nv[0] = strdup("method"); nv[1] = strdup("GET"); nv[2] = strdup("scheme"); nv[3] = strdup("https"); nv[4] = strdup("url"); nv[5] = strdup(path); nv[6] = strdup("version"); nv[7] = strdup("HTTP/1.1"); nv[8] = NULL; spdylay_frame_syn_stream_init(&frame->syn_stream, SPDYLAY_FLAG_FIN, 0, 0, 0, nv); r = spdylay_session_add_frame(session, SPDYLAY_SYN_STREAM, frame); assert(r == 0); }