libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit 639c6a9880b73c1bbef0944e8934cece1e5191ac
parent 7f193935fa7ec246bfa619d0fd7d6ce1a059fb1c
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun,  5 May 2013 19:21:40 +0000

merging libmicrospdy into tree

Diffstat:
MAUTHORS | 1+
MMakefile.am | 2+-
MREADME | 127++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mconfigure.ac | 44++++++++++++++++++++++++++++++++++++++++++++
Mdoc/Makefile.am | 3++-
Adoc/spdy-draft.txt | 2856+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alibmicrospdy.pc.in | 13+++++++++++++
Am4/openssl.m4 | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Makefile.am | 11++++++++++-
Asrc/datadir/cert-and-key-for-wireshark.pem | 15+++++++++++++++
Asrc/datadir/cert-and-key.pem | 34++++++++++++++++++++++++++++++++++
Msrc/examples/Makefile.am | 58+++++++++++++++++++++++++++++++++++++++++++++++++---------
Asrc/examples/spdy_event_loop.c | 444+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/examples/spdy_fileserver.c | 340+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/examples/spdy_response_with_callback.c | 230+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/Makefile.am | 7++++++-
Asrc/include/microspdy.h | 1275+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/EXPORT.sym | 2++
Asrc/microspdy/Makefile.am | 38++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/alstructures.c | 41+++++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/alstructures.h | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/applicationlayer.c | 679+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/applicationlayer.h | 31+++++++++++++++++++++++++++++++
Asrc/microspdy/compression.c | 441+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/compression.h | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/daemon.c | 515+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/daemon.h | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/internal.c | 38++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/internal.h | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/session.c | 1554+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/session.h | 248+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/stream.c | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/stream.h | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/structures.c | 612+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/structures.h | 1128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/tls.c | 255+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/microspdy/tls.h | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/spdy2http/Makefile.am | 35+++++++++++++++++++++++++++++++++++
Asrc/spdy2http/proxy.c | 625+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
39 files changed, 12615 insertions(+), 49 deletions(-)

diff --git a/AUTHORS b/AUTHORS @@ -1,5 +1,6 @@ Primary developers: Christian Grothoff <christian@grothoff.org> (maintainer) +Andrey Uzunov <andrey.uzunov@gmail.com> (maintainer for libmicrospdy) Nils Durner <durner@gnunet.org> (W32 port) Sagie Amir (TLS/SSL support using GNUtls) Richard Alimi <rich@velvetsea.net> (performance) diff --git a/Makefile.am b/Makefile.am @@ -1,6 +1,6 @@ ACLOCAL_AMFLAGS = -I m4 SUBDIRS = contrib src doc m4 . -EXTRA_DIST = acinclude.m4 libmicrohttpd.pc.in +EXTRA_DIST = acinclude.m4 libmicrohttpd.pc.in libmicrospdy.pc.in pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libmicrohttpd.pc diff --git a/README b/README @@ -7,6 +7,11 @@ is also supported). GNU libmicrohttpd only implements the HTTP 1.1 protocol. The main application must still provide the application logic to generate the content. +Additionally, a second, still very experimental library is provided +for SPDY/HTTP 2.0 support. libmicrospdy provides a compact API and +implementation of SPDY server. libmicrospdy currently only implements +version 3 of SPDY and accepts only TLS connections. + Installation ============ @@ -20,6 +25,21 @@ Especially for development, do use the MHD_USE_DEBUG option to get error messages. +Requirements for libmicrospdy +============================= + +The following packages are needed to build libmicrospdy: + +* zlib +* OpenSSL >= 1.0.1 + +To run the test cases, involving requests, version of Spdylay, supporting +SPDY v3, is required. Spdylay is still under development and can be +found here: + +http://spdylay.sourceforge.net/ + + Configure options ================= @@ -53,58 +73,93 @@ must call "MHD_init" before using any MHD functions and "MHD_fini" after you are done using MHD. -Notes on compiling on z/OS: ---------------------------- - -After extracting the archive, run - -iconv -f UTF-8 -t IBM-1047 contrib/ascebc > /tmp/ascebc.sh -chmod +x /tmp/ascebc.sh -for n in `find * -type f` -do - /tmp/ascebc.sh $n -done - -to convert all source files to EBCDIC. Note that you must run -"configure" from the directory where the configure script is -located. Otherwise, configure will fail to find the -"contrib/xcc" script (which is a wrapper around the z/OS c89 -compiler). - - Development Status ================== -This is a beta release. Below we list things that should be -implemented (in order of importance) before we can claim to be -reasonably complete. - +This is a beta release for libmicrohttpd. Before declaring the +library stable, we should implement support for HTTP "Upgrade" +requests and have testcases for the following features: -Untested features: -================== -- add testcases for http/1.1 pipelining (need - to figure out how to ensure curl pipelines +- HTTP/1.1 pipelining (need to figure out how to ensure curl pipelines -- and it seems libcurl has issues with pipelining, see http://curl.haxx.se/mail/lib-2007-12/0248.html) -- add testcases for resource limit enforcement -- add testcases for client queuing early response, - suppressing 100 CONTINUE -- extend testcase for chunked encoding to validate - handling of footers +- resource limit enforcement +- client queuing early response, suppressing 100 CONTINUE +- chunked encoding to validate handling of footers - more testing for SSL support - MHD basic and digest authentication - -Functions not covered by "make check": -====================================== +In particular, the following functions are not covered by 'make check': - mhd_panic_std (daemon.c); special case (abort) - parse_options (daemon.c) - MHD_set_panic_func (daemon.c) - MHD_get_version (daemon.c) +This is an early alpha release for libmicrospdy. The following things +should be implemented (in order of importance) before we can claim to +be reasonably complete: +- Change session timeout to use not seconds but something more precise +- SPDY RST_STREAM sending on each possible error (DONE?) +- SPDY_close_session +- Find the best way for closing still opened stream (new call or existing) +- SPDY_is_stream_opened +- SPDY PING (used often by browsers) +- SPDY WINDOW_UPDATE - used often by browsers +- SPDY Settings +- SPDY PUSH +- SPDY HEADERS +- HTTP POST over SPDY and receiving DATA frames +- HTTP PUT over SPDY +- SPDY Credentials + +Additional ideas for features include: +- Individual callbacks for each session +- Individual timeout for each session +- Setting number of frames that can be written to the output at once. + A big number means faster sending of a big resource, but the other + sessions will wait longer. + +Unimplemented API functions of libmicrospdy: +- SPDY_settings_create (); +- SPDY_settings_add (...); +- SPDY_settings_lookup (...); +- SPDY_settings_iterate (...); +- SPDY_settings_destroy (...); +- SPDY_close_session(...); +- SPDY_send_ping(...); +- SPDY_send_settings (...); + +In particular, we should write tests for: +- Enqueueing responses while considering request priorities. + + + + + Missing documentation: ====================== -- manual: +- libmicrohttpd manual: * document details on porting MHD (plibc, z/OS) +- libmicrospdy manual: + * missing entirely + + +Notes on compiling on z/OS: +=========================== + +After extracting the archive, run + +iconv -f UTF-8 -t IBM-1047 contrib/ascebc > /tmp/ascebc.sh +chmod +x /tmp/ascebc.sh +for n in `find * -type f` +do + /tmp/ascebc.sh $n +done + +to convert all source files to EBCDIC. Note that you must run +"configure" from the directory where the configure script is +located. Otherwise, configure will fail to find the +"contrib/xcc" script (which is a wrapper around the z/OS c89 +compiler). diff --git a/configure.ac b/configure.ac @@ -34,6 +34,14 @@ AC_SUBST(LIB_VERSION_CURRENT) AC_SUBST(LIB_VERSION_REVISION) AC_SUBST(LIB_VERSION_AGE) +LIBSPDY_VERSION_CURRENT=0 +LIBSPDY_VERSION_REVISION=0 +LIBSPDY_VERSION_AGE=0 +AC_SUBST(LIBSPDY_VERSION_CURRENT) +AC_SUBST(LIBSPDY_VERSION_REVISION) +AC_SUBST(LIBSPDY_VERSION_AGE) + + if test `uname -s` = "OS/390" then # configure binaries for z/OS @@ -263,6 +271,39 @@ AC_CHECK_LIB(magic, magic_open, AM_CONDITIONAL(HAVE_MAGIC, false))], AM_CONDITIONAL(HAVE_MAGIC, false)) + +# optional: libmicrospdy support. Enabled by default +AC_MSG_CHECKING(whether to support libmicrospdy) +AC_ARG_ENABLE([spdy], + AS_HELP_STRING([--disable-spdy], + [disable libmicrospdy]), + [enable_spdy=${enableval}], + [enable_spdy=yes]) +if test "$enable_spdy" = "yes" +then + AC_DEFINE([SPDY_SUPPORT],[1],[include libmicrospdy support]) +else + AC_DEFINE([SPDY_SUPPORT],[0],[disable libmicrospdy support]) +fi +AC_MSG_RESULT($enable_spdy) +AM_CONDITIONAL(ENABLE_SPDY, [test "x$enable_spdy" != "xno"]) + +spdy_OPENSSL +# for pkg-config +SPDY_LIBDEPS="" + + +AC_SUBST(SPDY_LIB_LDFLAGS) +# for pkg-config +AC_SUBST(SPDY_LIBDEPS) + +AM_CONDITIONAL(ENABLE_MINITASN1, [test -n " " ] ) +AM_CONDITIONAL(ENABLE_OPENSSL, [test -n "" ] ) +AM_CONDITIONAL(HAVE_LD_OUTPUT_DEF, [test -n "" ] ) +AM_CONDITIONAL(HAVE_LD_VERSION_SCRIPT, [test -n "" ] ) + + + LIBS=$SAVE_LIBS AM_CONDITIONAL(HAVE_CURL, test x$curl = x1) @@ -450,9 +491,12 @@ src/Makefile src/include/Makefile src/include/plibc/Makefile src/microhttpd/Makefile +src/microspdy/Makefile +src/spdy2http/Makefile src/examples/Makefile src/testcurl/Makefile src/testcurl/https/Makefile +src/testspdy/Makefile src/testzzuf/Makefile]) AC_OUTPUT diff --git a/doc/Makefile.am b/doc/Makefile.am @@ -25,4 +25,5 @@ microhttpd_TEXINFOS = \ lgpl.texi \ ecos.texi -EXTRA_DIST = $(man_MANS) Doxyfile $(microhttpd_TEXINFOS) +EXTRA_DIST = $(man_MANS) Doxyfile $(microhttpd_TEXINFOS) spdy-draft.txt + diff --git a/doc/spdy-draft.txt b/doc/spdy-draft.txt @@ -0,0 +1,2856 @@ + + + +Network Working Group M. Belshe +Internet-Draft Twist +Expires: August 4, 2012 R. Peon + Google, Inc + Feb 2012 + + + SPDY Protocol + draft-mbelshe-httpbis-spdy-00 + +Abstract + + This document describes SPDY, a protocol designed for low-latency + transport of content over the World Wide Web. SPDY introduces two + layers of protocol. The lower layer is a general purpose framing + layer which can be used atop a reliable transport (likely TCP) for + multiplexed, prioritized, and compressed data communication of many + concurrent streams. The upper layer of the protocol provides HTTP- + like RFC2616 [RFC2616] semantics for compatibility with existing HTTP + application servers. + +Status of this Memo + + This Internet-Draft is submitted in full conformance with the + provisions of BCP 78 and BCP 79. + + Internet-Drafts are working documents of the Internet Engineering + Task Force (IETF). Note that other groups may also distribute + working documents as Internet-Drafts. The list of current Internet- + Drafts is at http://datatracker.ietf.org/drafts/current/. + + Internet-Drafts are draft documents valid for a maximum of six months + and may be updated, replaced, or obsoleted by other documents at any + time. It is inappropriate to use Internet-Drafts as reference + material or to cite them other than as "work in progress." + + This Internet-Draft will expire on August 4, 2012. + +Copyright Notice + + Copyright (c) 2012 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info) in effect on the date of + publication of this document. Please review these documents + carefully, as they describe your rights and restrictions with respect + + + +Belshe & Peon Expires August 4, 2012 [Page 1] + +Internet-Draft SPDY Feb 2012 + + + to this document. Code Components extracted from this document must + include Simplified BSD License text as described in Section 4.e of + the Trust Legal Provisions and are provided without warranty as + described in the Simplified BSD License. + + +Table of Contents + + 1. Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 + 1.1. Document Organization . . . . . . . . . . . . . . . . . . 4 + 1.2. Definitions . . . . . . . . . . . . . . . . . . . . . . . 5 + 2. SPDY Framing Layer . . . . . . . . . . . . . . . . . . . . . . 6 + 2.1. Session (Connections) . . . . . . . . . . . . . . . . . . 6 + 2.2. Framing . . . . . . . . . . . . . . . . . . . . . . . . . 6 + 2.2.1. Control frames . . . . . . . . . . . . . . . . . . . . 6 + 2.2.2. Data frames . . . . . . . . . . . . . . . . . . . . . 7 + 2.3. Streams . . . . . . . . . . . . . . . . . . . . . . . . . 8 + 2.3.1. Stream frames . . . . . . . . . . . . . . . . . . . . 9 + 2.3.2. Stream creation . . . . . . . . . . . . . . . . . . . 9 + 2.3.3. Stream priority . . . . . . . . . . . . . . . . . . . 10 + 2.3.4. Stream headers . . . . . . . . . . . . . . . . . . . . 10 + 2.3.5. Stream data exchange . . . . . . . . . . . . . . . . . 10 + 2.3.6. Stream half-close . . . . . . . . . . . . . . . . . . 10 + 2.3.7. Stream close . . . . . . . . . . . . . . . . . . . . . 11 + 2.4. Error Handling . . . . . . . . . . . . . . . . . . . . . . 11 + 2.4.1. Session Error Handling . . . . . . . . . . . . . . . . 11 + 2.4.2. Stream Error Handling . . . . . . . . . . . . . . . . 12 + 2.5. Data flow . . . . . . . . . . . . . . . . . . . . . . . . 12 + 2.6. Control frame types . . . . . . . . . . . . . . . . . . . 12 + 2.6.1. SYN_STREAM . . . . . . . . . . . . . . . . . . . . . . 12 + 2.6.2. SYN_REPLY . . . . . . . . . . . . . . . . . . . . . . 14 + 2.6.3. RST_STREAM . . . . . . . . . . . . . . . . . . . . . . 15 + 2.6.4. SETTINGS . . . . . . . . . . . . . . . . . . . . . . . 16 + 2.6.5. PING . . . . . . . . . . . . . . . . . . . . . . . . . 19 + 2.6.6. GOAWAY . . . . . . . . . . . . . . . . . . . . . . . . 20 + 2.6.7. HEADERS . . . . . . . . . . . . . . . . . . . . . . . 21 + 2.6.8. WINDOW_UPDATE . . . . . . . . . . . . . . . . . . . . 22 + 2.6.9. CREDENTIAL . . . . . . . . . . . . . . . . . . . . . . 24 + 2.6.10. Name/Value Header Block . . . . . . . . . . . . . . . 26 + 3. HTTP Layering over SPDY . . . . . . . . . . . . . . . . . . . 33 + 3.1. Connection Management . . . . . . . . . . . . . . . . . . 33 + 3.1.1. Use of GOAWAY . . . . . . . . . . . . . . . . . . . . 33 + 3.2. HTTP Request/Response . . . . . . . . . . . . . . . . . . 34 + 3.2.1. Request . . . . . . . . . . . . . . . . . . . . . . . 34 + 3.2.2. Response . . . . . . . . . . . . . . . . . . . . . . . 35 + 3.2.3. Authentication . . . . . . . . . . . . . . . . . . . . 36 + 3.3. Server Push Transactions . . . . . . . . . . . . . . . . . 37 + 3.3.1. Server implementation . . . . . . . . . . . . . . . . 38 + + + +Belshe & Peon Expires August 4, 2012 [Page 2] + +Internet-Draft SPDY Feb 2012 + + + 3.3.2. Client implementation . . . . . . . . . . . . . . . . 39 + 4. Design Rationale and Notes . . . . . . . . . . . . . . . . . . 40 + 4.1. Separation of Framing Layer and Application Layer . . . . 40 + 4.2. Error handling - Framing Layer . . . . . . . . . . . . . . 40 + 4.3. One Connection Per Domain . . . . . . . . . . . . . . . . 40 + 4.4. Fixed vs Variable Length Fields . . . . . . . . . . . . . 41 + 4.5. Compression Context(s) . . . . . . . . . . . . . . . . . . 41 + 4.6. Unidirectional streams . . . . . . . . . . . . . . . . . . 42 + 4.7. Data Compression . . . . . . . . . . . . . . . . . . . . . 42 + 4.8. Server Push . . . . . . . . . . . . . . . . . . . . . . . 42 + 5. Security Considerations . . . . . . . . . . . . . . . . . . . 43 + 5.1. Use of Same-origin constraints . . . . . . . . . . . . . . 43 + 5.2. HTTP Headers and SPDY Headers . . . . . . . . . . . . . . 43 + 5.3. Cross-Protocol Attacks . . . . . . . . . . . . . . . . . . 43 + 5.4. Server Push Implicit Headers . . . . . . . . . . . . . . . 43 + 6. Privacy Considerations . . . . . . . . . . . . . . . . . . . . 44 + 6.1. Long Lived Connections . . . . . . . . . . . . . . . . . . 44 + 6.2. SETTINGS frame . . . . . . . . . . . . . . . . . . . . . . 44 + 7. Incompatibilities with SPDY draft #2 . . . . . . . . . . . . . 45 + 8. Requirements Notation . . . . . . . . . . . . . . . . . . . . 46 + 9. Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . 47 + 10. Normative References . . . . . . . . . . . . . . . . . . . . . 48 + Appendix A. Changes . . . . . . . . . . . . . . . . . . . . . . . 50 + Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . . 51 + + + + + + + + + + + + + + + + + + + + + + + + + + + +Belshe & Peon Expires August 4, 2012 [Page 3] + +Internet-Draft SPDY Feb 2012 + + +1. Overview + + One of the bottlenecks of HTTP implementations is that HTTP relies on + multiple connections for concurrency. This causes several problems, + including additional round trips for connection setup, slow-start + delays, and connection rationing by the client, where it tries to + avoid opening too many connections to any single server. HTTP + pipelining helps some, but only achieves partial multiplexing. In + addition, pipelining has proven non-deployable in existing browsers + due to intermediary interference. + + SPDY adds a framing layer for multiplexing multiple, concurrent + streams across a single TCP connection (or any reliable transport + stream). The framing layer is optimized for HTTP-like request- + response streams, such that applications which run over HTTP today + can work over SPDY with little or no change on behalf of the web + application writer. + + The SPDY session offers four improvements over HTTP: + + Multiplexed requests: There is no limit to the number of requests + that can be issued concurrently over a single SPDY connection. + + Prioritized requests: Clients can request certain resources to be + delivered first. This avoids the problem of congesting the + network channel with non-critical resources when a high-priority + request is pending. + + Compressed headers: Clients today send a significant amount of + redundant data in the form of HTTP headers. Because a single web + page may require 50 or 100 subrequests, this data is significant. + + Server pushed streams: Server Push enables content to be pushed + from servers to clients without a request. + + SPDY attempts to preserve the existing semantics of HTTP. All + features such as cookies, ETags, Vary headers, Content-Encoding + negotiations, etc work as they do with HTTP; SPDY only replaces the + way the data is written to the network. + +1.1. Document Organization + + The SPDY Specification is split into two parts: a framing layer + (Section 2), which multiplexes a TCP connection into independent, + length-prefixed frames, and an HTTP layer (Section 3), which + specifies the mechanism for overlaying HTTP request/response pairs on + top of the framing layer. While some of the framing layer concepts + are isolated from the HTTP layer, building a generic framing layer + + + +Belshe & Peon Expires August 4, 2012 [Page 4] + +Internet-Draft SPDY Feb 2012 + + + has not been a goal. The framing layer is tailored to the needs of + the HTTP protocol and server push. + +1.2. Definitions + + client: The endpoint initiating the SPDY session. + + connection: A transport-level connection between two endpoints. + + endpoint: Either the client or server of a connection. + + frame: A header-prefixed sequence of bytes sent over a SPDY + session. + + server: The endpoint which did not initiate the SPDY session. + + session: A synonym for a connection. + + session error: An error on the SPDY session. + + stream: A bi-directional flow of bytes across a virtual channel + within a SPDY session. + + stream error: An error on an individual SPDY stream. + + + + + + + + + + + + + + + + + + + + + + + + + + + +Belshe & Peon Expires August 4, 2012 [Page 5] + +Internet-Draft SPDY Feb 2012 + + +2. SPDY Framing Layer + +2.1. Session (Connections) + + The SPDY framing layer (or "session") runs atop a reliable transport + layer such as TCP [RFC0793]. The client is the TCP connection + initiator. SPDY connections are persistent connections. + + For best performance, it is expected that clients will not close open + connections until the user navigates away from all web pages + referencing a connection, or until the server closes the connection. + Servers are encouraged to leave connections open for as long as + possible, but can terminate idle connections if necessary. When + either endpoint closes the transport-level connection, it MUST first + send a GOAWAY (Section 2.6.6) frame so that the endpoints can + reliably determine if requests finished before the close. + +2.2. Framing + + Once the connection is established, clients and servers exchange + framed messages. There are two types of frames: control frames + (Section 2.2.1) and data frames (Section 2.2.2). Frames always have + a common header which is 8 bytes in length. + + The first bit is a control bit indicating whether a frame is a + control frame or data frame. Control frames carry a version number, + a frame type, flags, and a length. Data frames contain the stream + ID, flags, and the length for the payload carried after the common + header. The simple header is designed to make reading and writing of + frames easy. + + All integer values, including length, version, and type, are in + network byte order. SPDY does not enforce alignment of types in + dynamically sized frames. + +2.2.1. Control frames + + +----------------------------------+ + |C| Version(15bits) | Type(16bits) | + +----------------------------------+ + | Flags (8) | Length (24 bits) | + +----------------------------------+ + | Data | + +----------------------------------+ + + Control bit: The 'C' bit is a single bit indicating if this is a + control message. For control frames this value is always 1. + + + + +Belshe & Peon Expires August 4, 2012 [Page 6] + +Internet-Draft SPDY Feb 2012 + + + Version: The version number of the SPDY protocol. This document + describes SPDY version 3. + + Type: The type of control frame. See Control Frames for the complete + list of control frames. + + Flags: Flags related to this frame. Flags for control frames and + data frames are different. + + Length: An unsigned 24-bit value representing the number of bytes + after the length field. + + Data: data associated with this control frame. The format and length + of this data is controlled by the control frame type. + + Control frame processing requirements: + + Note that full length control frames (16MB) can be large for + implementations running on resource-limited hardware. In such + cases, implementations MAY limit the maximum length frame + supported. However, all implementations MUST be able to receive + control frames of at least 8192 octets in length. + +2.2.2. Data frames + + +----------------------------------+ + |C| Stream-ID (31bits) | + +----------------------------------+ + | Flags (8) | Length (24 bits) | + +----------------------------------+ + | Data | + +----------------------------------+ + + Control bit: For data frames this value is always 0. + + Stream-ID: A 31-bit value identifying the stream. + + Flags: Flags related to this frame. Valid flags are: + + 0x01 = FLAG_FIN - signifies that this frame represents the last + frame to be transmitted on this stream. See Stream Close + (Section 2.3.7) below. + + 0x02 = FLAG_COMPRESS - indicates that the data in this frame has + been compressed. + + Length: An unsigned 24-bit value representing the number of bytes + after the length field. The total size of a data frame is 8 bytes + + + + +Belshe & Peon Expires August 4, 2012 [Page 7] + +Internet-Draft SPDY Feb 2012 + + + length. It is valid to have a zero-length data frame. + + Data: The variable-length data payload; the length was defined in the + length field. + + Data frame processing requirements: + + If an endpoint receives a data frame for a stream-id which is not + open and the endpoint has not sent a GOAWAY (Section 2.6.6) frame, + it MUST send issue a stream error (Section 2.4.2) with the error + code INVALID_STREAM for the stream-id. + + If the endpoint which created the stream receives a data frame + before receiving a SYN_REPLY on that stream, it is a protocol + error, and the recipient MUST issue a stream error (Section 2.4.2) + with the status code PROTOCOL_ERROR for the stream-id. + + Implementors note: If an endpoint receives multiple data frames + for invalid stream-ids, it MAY close the session. + + All SPDY endpoints MUST accept compressed data frames. + Compression of data frames is always done using zlib compression. + Each stream initializes and uses its own compression context + dedicated to use within that stream. Endpoints are encouraged to + use application level compression rather than SPDY stream level + compression. + + Each SPDY stream sending compressed frames creates its own zlib + context for that stream, and these compression contexts MUST be + distinct from the compression contexts used with SYN_STREAM/ + SYN_REPLY/HEADER compression. (Thus, if both endpoints of a + stream are compressing data on the stream, there will be two zlib + contexts, one for sending and one for receiving). + +2.3. Streams + + Streams are independent sequences of bi-directional data divided into + frames with several properties: + + Streams may be created by either the client or server. + + Streams optionally carry a set of name/value header pairs. + + Streams can concurrently send data interleaved with other streams. + + Streams may be cancelled. + + + + + +Belshe & Peon Expires August 4, 2012 [Page 8] + +Internet-Draft SPDY Feb 2012 + + +2.3.1. Stream frames + + SPDY defines 3 control frames to manage the lifecycle of a stream: + + SYN_STREAM - Open a new stream + + SYN_REPLY - Remote acknowledgement of a new, open stream + + RST_STREAM - Close a stream + +2.3.2. Stream creation + + A stream is created by sending a control frame with the type set to + SYN_STREAM (Section 2.6.1). If the server is initiating the stream, + the Stream-ID must be even. If the client is initiating the stream, + the Stream-ID must be odd. 0 is not a valid Stream-ID. Stream-IDs + from each side of the connection must increase monotonically as new + streams are created. E.g. Stream 2 may be created after stream 3, + but stream 7 must not be created after stream 9. Stream IDs do not + wrap: when a client or server cannot create a new stream id without + exceeding a 31 bit value, it MUST NOT create a new stream. + + The stream-id MUST increase with each new stream. If an endpoint + receives a SYN_STREAM with a stream id which is less than any + previously received SYN_STREAM, it MUST issue a session error + (Section 2.4.1) with the status PROTOCOL_ERROR. + + It is a protocol error to send two SYN_STREAMs with the same + stream-id. If a recipient receives a second SYN_STREAM for the same + stream, it MUST issue a stream error (Section 2.4.2) with the status + code PROTOCOL_ERROR. + + Upon receipt of a SYN_STREAM, the recipient can reject the stream by + sending a stream error (Section 2.4.2) with the error code + REFUSED_STREAM. Note, however, that the creating endpoint may have + already sent additional frames for that stream which cannot be + immediately stopped. + + Once the stream is created, the creator may immediately send HEADERS + or DATA frames for that stream, without needing to wait for the + recipient to acknowledge. + +2.3.2.1. Unidirectional streams + + When an endpoint creates a stream with the FLAG_UNIDIRECTIONAL flag + set, it creates a unidirectional stream which the creating endpoint + can use to send frames, but the receiving endpoint cannot. The + receiving endpoint is implicitly already in the half-closed + + + +Belshe & Peon Expires August 4, 2012 [Page 9] + +Internet-Draft SPDY Feb 2012 + + + (Section 2.3.6) state. + +2.3.2.2. Bidirectional streams + + SYN_STREAM frames which do not use the FLAG_UNIDIRECTIONAL flag are + bidirectional streams. Both endpoints can send data on a bi- + directional stream. + +2.3.3. Stream priority + + The creator of a stream assigns a priority for that stream. Priority + is represented as an integer from 0 to 7. 0 represents the highest + priority and 7 represents the lowest priority. + + The sender and recipient SHOULD use best-effort to process streams in + the order of highest priority to lowest priority. + +2.3.4. Stream headers + + Streams carry optional sets of name/value pair headers which carry + metadata about the stream. After the stream has been created, and as + long as the sender is not closed (Section 2.3.7) or half-closed + (Section 2.3.6), each side may send HEADERS frame(s) containing the + header data. Header data can be sent in multiple HEADERS frames, and + HEADERS frames may be interleaved with data frames. + +2.3.5. Stream data exchange + + Once a stream is created, it can be used to send arbitrary amounts of + data. Generally this means that a series of data frames will be sent + on the stream until a frame containing the FLAG_FIN flag is set. The + FLAG_FIN can be set on a SYN_STREAM (Section 2.6.1), SYN_REPLY + (Section 2.6.2), HEADERS (Section 2.6.7) or a DATA (Section 2.2.2) + frame. Once the FLAG_FIN has been sent, the stream is considered to + be half-closed. + +2.3.6. Stream half-close + + When one side of the stream sends a frame with the FLAG_FIN flag set, + the stream is half-closed from that endpoint. The sender of the + FLAG_FIN MUST NOT send further frames on that stream. When both + sides have half-closed, the stream is closed. + + If an endpoint receives a data frame after the stream is half-closed + from the sender (e.g. the endpoint has already received a prior frame + for the stream with the FIN flag set), it MUST send a RST_STREAM to + the sender with the status STREAM_ALREADY_CLOSED. + + + + +Belshe & Peon Expires August 4, 2012 [Page 10] + +Internet-Draft SPDY Feb 2012 + + +2.3.7. Stream close + + There are 3 ways that streams can be terminated: + + Normal termination: Normal stream termination occurs when both + sender and recipient have half-closed the stream by sending a + FLAG_FIN. + + Abrupt termination: Either the client or server can send a + RST_STREAM control frame at any time. A RST_STREAM contains an + error code to indicate the reason for failure. When a RST_STREAM + is sent from the stream originator, it indicates a failure to + complete the stream and that no further data will be sent on the + stream. When a RST_STREAM is sent from the stream recipient, the + sender, upon receipt, should stop sending any data on the stream. + The stream recipient should be aware that there is a race between + data already in transit from the sender and the time the + RST_STREAM is received. See Stream Error Handling (Section 2.4.2) + + TCP connection teardown: If the TCP connection is torn down while + un-closed streams exist, then the endpoint must assume that the + stream was abnormally interrupted and may be incomplete. + + If an endpoint receives a data frame after the stream is closed, it + must send a RST_STREAM to the sender with the status PROTOCOL_ERROR. + +2.4. Error Handling + + The SPDY framing layer has only two types of errors, and they are + always handled consistently. Any reference in this specification to + "issue a session error" refers to Section 2.4.1. Any reference to + "issue a stream error" refers to Section 2.4.2. + +2.4.1. Session Error Handling + + A session error is any error which prevents further processing of the + framing layer or which corrupts the session compression state. When + a session error occurs, the endpoint encountering the error MUST + first send a GOAWAY (Section 2.6.6) frame with the stream id of most + recently received stream from the remote endpoint, and the error code + for why the session is terminating. After sending the GOAWAY frame, + the endpoint MUST close the TCP connection. + + Note that the session compression state is dependent upon both + endpoints always processing all compressed data. If an endpoint + partially processes a frame containing compressed data without + updating compression state properly, future control frames which use + compression will be always be errored. Implementations SHOULD always + + + +Belshe & Peon Expires August 4, 2012 [Page 11] + +Internet-Draft SPDY Feb 2012 + + + try to process compressed data so that errors which could be handled + as stream errors do not become session errors. + + Note that because this GOAWAY is sent during a session error case, it + is possible that the GOAWAY will not be reliably received by the + receiving endpoint. It is a best-effort attempt to communicate with + the remote about why the session is going down. + +2.4.2. Stream Error Handling + + A stream error is an error related to a specific stream-id which does + not affect processing of other streams at the framing layer. Upon a + stream error, the endpoint MUST send a RST_STREAM (Section 2.6.3) + frame which contains the stream id of the stream where the error + occurred and the error status which caused the error. After sending + the RST_STREAM, the stream is closed to the sending endpoint. After + sending the RST_STREAM, if the sender receives any frames other than + a RST_STREAM for that stream id, it will result in sending additional + RST_STREAM frames. An endpoint MUST NOT send a RST_STREAM in + response to an RST_STREAM, as doing so would lead to RST_STREAM + loops. Sending a RST_STREAM does not cause the SPDY session to be + closed. + + If an endpoint has multiple RST_STREAM frames to send in succession + for the same stream-id and the same error code, it MAY coalesce them + into a single RST_STREAM frame. (This can happen if a stream is + closed, but the remote sends multiple data frames. There is no + reason to send a RST_STREAM for each frame in succession). + +2.5. Data flow + + Because TCP provides a single stream of data on which SPDY + multiplexes multiple logical streams, clients and servers must + intelligently interleave data messages for concurrent sessions. + +2.6. Control frame types + +2.6.1. SYN_STREAM + + The SYN_STREAM control frame allows the sender to asynchronously + create a stream between the endpoints. See Stream Creation + (Section 2.3.2) + + + + + + + + + +Belshe & Peon Expires August 4, 2012 [Page 12] + +Internet-Draft SPDY Feb 2012 + + ++------------------------------------+ +|1| version | 1 | ++------------------------------------+ +| Flags (8) | Length (24 bits) | ++------------------------------------+ +|X| Stream-ID (31bits) | ++------------------------------------+ +|X| Associated-To-Stream-ID (31bits) | ++------------------------------------+ +| Pri|Unused | Slot | | ++-------------------+ | +| Number of Name/Value pairs (int32) | <+ ++------------------------------------+ | +| Length of name (int32) | | This section is the "Name/Value ++------------------------------------+ | Header Block", and is compressed. +| Name (string) | | ++------------------------------------+ | +| Length of value (int32) | | ++------------------------------------+ | +| Value (string) | | ++------------------------------------+ | +| (repeats) | <+ + + Flags: Flags related to this frame. Valid flags are: + + 0x01 = FLAG_FIN - marks this frame as the last frame to be + transmitted on this stream and puts the sender in the half-closed + (Section 2.3.6) state. + + 0x02 = FLAG_UNIDIRECTIONAL - a stream created with this flag puts + the recipient in the half-closed (Section 2.3.6) state. + + Length: The length is the number of bytes which follow the length + field in the frame. For SYN_STREAM frames, this is 10 bytes plus the + length of the compressed Name/Value block. + + Stream-ID: The 31-bit identifier for this stream. This stream-id + will be used in frames which are part of this stream. + + Associated-To-Stream-ID: The 31-bit identifier for a stream which + this stream is associated to. If this stream is independent of all + other streams, it should be 0. + + Priority: A 3-bit priority (Section 2.3.3) field. + + Unused: 5 bits of unused space, reserved for future use. + + Slot: An 8 bit unsigned integer specifying the index in the server's + + + +Belshe & Peon Expires August 4, 2012 [Page 13] + +Internet-Draft SPDY Feb 2012 + + + CREDENTIAL vector of the client certificate to be used for this + request. see CREDENTIAL frame (Section 2.6.9). The value 0 means no + client certificate should be associated with this stream. + + Name/Value Header Block: A set of name/value pairs carried as part of + the SYN_STREAM. see Name/Value Header Block (Section 2.6.10). + + If an endpoint receives a SYN_STREAM which is larger than the + implementation supports, it MAY send a RST_STREAM with error code + FRAME_TOO_LARGE. All implementations MUST support the minimum size + limits defined in the Control Frames section (Section 2.2.1). + +2.6.2. SYN_REPLY + + SYN_REPLY indicates the acceptance of a stream creation by the + recipient of a SYN_STREAM frame. + ++------------------------------------+ +|1| version | 2 | ++------------------------------------+ +| Flags (8) | Length (24 bits) | ++------------------------------------+ +|X| Stream-ID (31bits) | ++------------------------------------+ +| Number of Name/Value pairs (int32) | <+ ++------------------------------------+ | +| Length of name (int32) | | This section is the "Name/Value ++------------------------------------+ | Header Block", and is compressed. +| Name (string) | | ++------------------------------------+ | +| Length of value (int32) | | ++------------------------------------+ | +| Value (string) | | ++------------------------------------+ | +| (repeats) | <+ + + Flags: Flags related to this frame. Valid flags are: + + 0x01 = FLAG_FIN - marks this frame as the last frame to be + transmitted on this stream and puts the sender in the half-closed + (Section 2.3.6) state. + + Length: The length is the number of bytes which follow the length + field in the frame. For SYN_REPLY frames, this is 4 bytes plus the + length of the compressed Name/Value block. + + Stream-ID: The 31-bit identifier for this stream. + + + + +Belshe & Peon Expires August 4, 2012 [Page 14] + +Internet-Draft SPDY Feb 2012 + + + If an endpoint receives multiple SYN_REPLY frames for the same active + stream ID, it MUST issue a stream error (Section 2.4.2) with the + error code STREAM_IN_USE. + + Name/Value Header Block: A set of name/value pairs carried as part of + the SYN_STREAM. see Name/Value Header Block (Section 2.6.10). + + If an endpoint receives a SYN_REPLY which is larger than the + implementation supports, it MAY send a RST_STREAM with error code + FRAME_TOO_LARGE. All implementations MUST support the minimum size + limits defined in the Control Frames section (Section 2.2.1). + +2.6.3. RST_STREAM + + The RST_STREAM frame allows for abnormal termination of a stream. + When sent by the creator of a stream, it indicates the creator wishes + to cancel the stream. When sent by the recipient of a stream, it + indicates an error or that the recipient did not want to accept the + stream, so the stream should be closed. + + +----------------------------------+ + |1| version | 3 | + +----------------------------------+ + | Flags (8) | 8 | + +----------------------------------+ + |X| Stream-ID (31bits) | + +----------------------------------+ + | Status code | + +----------------------------------+ + + Flags: Flags related to this frame. RST_STREAM does not define any + flags. This value must be 0. + + Length: An unsigned 24-bit value representing the number of bytes + after the length field. For RST_STREAM control frames, this value is + always 8. + + Stream-ID: The 31-bit identifier for this stream. + + Status code: (32 bits) An indicator for why the stream is being + terminated.The following status codes are defined: + + 1 - PROTOCOL_ERROR. This is a generic error, and should only be + used if a more specific error is not available. + + 2 - INVALID_STREAM. This is returned when a frame is received for + a stream which is not active. + + + + +Belshe & Peon Expires August 4, 2012 [Page 15] + +Internet-Draft SPDY Feb 2012 + + + 3 - REFUSED_STREAM. Indicates that the stream was refused before + any processing has been done on the stream. + + 4 - UNSUPPORTED_VERSION. Indicates that the recipient of a stream + does not support the SPDY version requested. + + 5 - CANCEL. Used by the creator of a stream to indicate that the + stream is no longer needed. + + 6 - INTERNAL_ERROR. This is a generic error which can be used + when the implementation has internally failed, not due to anything + in the protocol. + + 7 - FLOW_CONTROL_ERROR. The endpoint detected that its peer + violated the flow control protocol. + + 8 - STREAM_IN_USE. The endpoint received a SYN_REPLY for a stream + already open. + + 9 - STREAM_ALREADY_CLOSED. The endpoint received a data or + SYN_REPLY frame for a stream which is half closed. + + 10 - INVALID_CREDENTIALS. The server received a request for a + resource whose origin does not have valid credentials in the + client certificate vector. + + 11 - FRAME_TOO_LARGE. The endpoint received a frame which this + implementation could not support. If FRAME_TOO_LARGE is sent for + a SYN_STREAM, HEADERS, or SYN_REPLY frame without fully processing + the compressed portion of those frames, then the compression state + will be out-of-sync with the other endpoint. In this case, + senders of FRAME_TOO_LARGE MUST close the session. + + Note: 0 is not a valid status code for a RST_STREAM. + + After receiving a RST_STREAM on a stream, the recipient must not send + additional frames for that stream, and the stream moves into the + closed state. + +2.6.4. SETTINGS + + A SETTINGS frame contains a set of id/value pairs for communicating + configuration data about how the two endpoints may communicate. + SETTINGS frames can be sent at any time by either endpoint, are + optionally sent, and are fully asynchronous. When the server is the + sender, the sender can request that configuration data be persisted + by the client across SPDY sessions and returned to the server in + future communications. + + + +Belshe & Peon Expires August 4, 2012 [Page 16] + +Internet-Draft SPDY Feb 2012 + + + Persistence of SETTINGS ID/Value pairs is done on a per origin/IP + pair (the "origin" is the set of scheme, host, and port from the URI. + See [RFC6454]). That is, when a client connects to a server, and the + server persists settings within the client, the client SHOULD return + the persisted settings on future connections to the same origin AND + IP address and TCP port. Clients MUST NOT request servers to use the + persistence features of the SETTINGS frames, and servers MUST ignore + persistence related flags sent by a client. + + +----------------------------------+ + |1| version | 4 | + +----------------------------------+ + | Flags (8) | Length (24 bits) | + +----------------------------------+ + | Number of entries | + +----------------------------------+ + | ID/Value Pairs | + | ... | + + Control bit: The control bit is always 1 for this message. + + Version: The SPDY version number. + + Type: The message type for a SETTINGS message is 4. + + Flags: FLAG_SETTINGS_CLEAR_SETTINGS (0x1): When set, the client + should clear any previously persisted SETTINGS ID/Value pairs. If + this frame contains ID/Value pairs with the + FLAG_SETTINGS_PERSIST_VALUE set, then the client will first clear its + existing, persisted settings, and then persist the values with the + flag set which are contained within this frame. Because persistence + is only implemented on the client, this flag can only be used when + the sender is the server. + + Length: An unsigned 24-bit value representing the number of bytes + after the length field. The total size of a SETTINGS frame is 8 + bytes + length. + + Number of entries: A 32-bit value representing the number of ID/value + pairs in this message. + + ID: A 32-bit ID number, comprised of 8 bits of flags and 24 bits of + unique ID. + + ID.flags: + + FLAG_SETTINGS_PERSIST_VALUE (0x1): When set, the sender of this + SETTINGS frame is requesting that the recipient persist the ID/ + + + +Belshe & Peon Expires August 4, 2012 [Page 17] + +Internet-Draft SPDY Feb 2012 + + + Value and return it in future SETTINGS frames sent from the + sender to this recipient. Because persistence is only + implemented on the client, this flag is only sent by the + server. + + FLAG_SETTINGS_PERSISTED (0x2): When set, the sender is + notifying the recipient that this ID/Value pair was previously + sent to the sender by the recipient with the + FLAG_SETTINGS_PERSIST_VALUE, and the sender is returning it. + Because persistence is only implemented on the client, this + flag is only sent by the client. + + Defined IDs: + + 1 - SETTINGS_UPLOAD_BANDWIDTH allows the sender to send its + expected upload bandwidth on this channel. This number is an + estimate. The value should be the integral number of kilobytes + per second that the sender predicts as an expected maximum + upload channel capacity. + + 2 - SETTINGS_DOWNLOAD_BANDWIDTH allows the sender to send its + expected download bandwidth on this channel. This number is an + estimate. The value should be the integral number of kilobytes + per second that the sender predicts as an expected maximum + download channel capacity. + + 3 - SETTINGS_ROUND_TRIP_TIME allows the sender to send its + expected round-trip-time on this channel. The round trip time + is defined as the minimum amount of time to send a control + frame from this client to the remote and receive a response. + The value is represented in milliseconds. + + 4 - SETTINGS_MAX_CONCURRENT_STREAMS allows the sender to inform + the remote endpoint the maximum number of concurrent streams + which it will allow. By default there is no limit. For + implementors it is recommended that this value be no smaller + than 100. + + 5 - SETTINGS_CURRENT_CWND allows the sender to inform the + remote endpoint of the current TCP CWND value. + + 6 - SETTINGS_DOWNLOAD_RETRANS_RATE allows the sender to inform + the remote endpoint the retransmission rate (bytes + retransmitted / total bytes transmitted). + + 7 - SETTINGS_INITIAL_WINDOW_SIZE allows the sender to inform + the remote endpoint the initial window size (in bytes) for new + streams. + + + +Belshe & Peon Expires August 4, 2012 [Page 18] + +Internet-Draft SPDY Feb 2012 + + + 8 - SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE allows the server + to inform the client if the new size of the client certificate + vector. + + Value: A 32-bit value. + + The message is intentionally extensible for future information which + may improve client-server communications. The sender does not need + to send every type of ID/value. It must only send those for which it + has accurate values to convey. When multiple ID/value pairs are + sent, they should be sent in order of lowest id to highest id. A + single SETTINGS frame MUST not contain multiple values for the same + ID. If the recipient of a SETTINGS frame discovers multiple values + for the same ID, it MUST ignore all values except the first one. + + A server may send multiple SETTINGS frames containing different ID/ + Value pairs. When the same ID/Value is sent twice, the most recent + value overrides any previously sent values. If the server sends IDs + 1, 2, and 3 with the FLAG_SETTINGS_PERSIST_VALUE in a first SETTINGS + frame, and then sends IDs 4 and 5 with the + FLAG_SETTINGS_PERSIST_VALUE, when the client returns the persisted + state on its next SETTINGS frame, it SHOULD send all 5 settings (1, + 2, 3, 4, and 5 in this example) to the server. + +2.6.5. PING + + The PING control frame is a mechanism for measuring a minimal round- + trip time from the sender. It can be sent from the client or the + server. Recipients of a PING frame should send an identical frame to + the sender as soon as possible (if there is other pending data + waiting to be sent, PING should take highest priority). Each ping + sent by a sender should use a unique ID. + + +----------------------------------+ + |1| version | 6 | + +----------------------------------+ + | 0 (flags) | 4 (length) | + +----------------------------------| + | 32-bit ID | + +----------------------------------+ + + Control bit: The control bit is always 1 for this message. + + Version: The SPDY version number. + + Type: The message type for a PING message is 6. + + Length: This frame is always 4 bytes long. + + + +Belshe & Peon Expires August 4, 2012 [Page 19] + +Internet-Draft SPDY Feb 2012 + + + ID: A unique ID for this ping, represented as an unsigned 32 bit + value. When the client initiates a ping, it must use an odd numbered + ID. When the server initiates a ping, it must use an even numbered + ping. Use of odd/even IDs is required in order to avoid accidental + looping on PINGs (where each side initiates an identical PING at the + same time). + + Note: If a sender uses all possible PING ids (e.g. has sent all 2^31 + possible IDs), it can wrap and start re-using IDs. + + If a server receives an even numbered PING which it did not initiate, + it must ignore the PING. If a client receives an odd numbered PING + which it did not initiate, it must ignore the PING. + +2.6.6. GOAWAY + + The GOAWAY control frame is a mechanism to tell the remote side of + the connection to stop creating streams on this session. It can be + sent from the client or the server. Once sent, the sender will not + respond to any new SYN_STREAMs on this session. Recipients of a + GOAWAY frame must not send additional streams on this session, + although a new session can be established for new streams. The + purpose of this message is to allow an endpoint to gracefully stop + accepting new streams (perhaps for a reboot or maintenance), while + still finishing processing of previously established streams. + + There is an inherent race condition between an endpoint sending + SYN_STREAMs and the remote sending a GOAWAY message. To deal with + this case, the GOAWAY contains a last-stream-id indicating the + stream-id of the last stream which was created on the sending + endpoint in this session. If the receiver of the GOAWAY sent new + SYN_STREAMs for sessions after this last-stream-id, they were not + processed by the server and the receiver may treat the stream as + though it had never been created at all (hence the receiver may want + to re-create the stream later on a new session). + + Endpoints should always send a GOAWAY message before closing a + connection so that the remote can know whether a stream has been + partially processed or not. (For example, if an HTTP client sends a + POST at the same time that a server closes a connection, the client + cannot know if the server started to process that POST request if the + server does not send a GOAWAY frame to indicate where it stopped + working). + + After sending a GOAWAY message, the sender must ignore all SYN_STREAM + frames for new streams. + + + + + +Belshe & Peon Expires August 4, 2012 [Page 20] + +Internet-Draft SPDY Feb 2012 + + + +----------------------------------+ + |1| version | 7 | + +----------------------------------+ + | 0 (flags) | 8 (length) | + +----------------------------------| + |X| Last-good-stream-ID (31 bits) | + +----------------------------------+ + | Status code | + +----------------------------------+ + + Control bit: The control bit is always 1 for this message. + + Version: The SPDY version number. + + Type: The message type for a GOAWAY message is 7. + + Length: This frame is always 8 bytes long. + + Last-good-stream-Id: The last stream id which was replied to (with + either a SYN_REPLY or RST_STREAM) by the sender of the GOAWAY + message. If no streams were replied to, this value MUST be 0. + + Status: The reason for closing the session. + + 0 - OK. This is a normal session teardown. + + 1 - PROTOCOL_ERROR. This is a generic error, and should only be + used if a more specific error is not available. + + 11 - INTERNAL_ERROR. This is a generic error which can be used + when the implementation has internally failed, not due to anything + in the protocol. + +2.6.7. HEADERS + + The HEADERS frame augments a stream with additional headers. It may + be optionally sent on an existing stream at any time. Specific + application of the headers in this frame is application-dependent. + The name/value header block within this frame is compressed. + + + + + + + + + + + + +Belshe & Peon Expires August 4, 2012 [Page 21] + +Internet-Draft SPDY Feb 2012 + + ++------------------------------------+ +|1| version | 8 | ++------------------------------------+ +| Flags (8) | Length (24 bits) | ++------------------------------------+ +|X| Stream-ID (31bits) | ++------------------------------------+ +| Number of Name/Value pairs (int32) | <+ ++------------------------------------+ | +| Length of name (int32) | | This section is the "Name/Value ++------------------------------------+ | Header Block", and is compressed. +| Name (string) | | ++------------------------------------+ | +| Length of value (int32) | | ++------------------------------------+ | +| Value (string) | | ++------------------------------------+ | +| (repeats) | <+ + + Flags: Flags related to this frame. Valid flags are: + + 0x01 = FLAG_FIN - marks this frame as the last frame to be + transmitted on this stream and puts the sender in the half-closed + (Section 2.3.6) state. + + Length: An unsigned 24 bit value representing the number of bytes + after the length field. The minimum length of the length field is 4 + (when the number of name value pairs is 0). + + Stream-ID: The stream this HEADERS block is associated with. + + Name/Value Header Block: A set of name/value pairs carried as part of + the SYN_STREAM. see Name/Value Header Block (Section 2.6.10). + +2.6.8. WINDOW_UPDATE + + The WINDOW_UPDATE control frame is used to implement per stream flow + control in SPDY. Flow control in SPDY is per hop, that is, only + between the two endpoints of a SPDY connection. If there are one or + more intermediaries between the client and the origin server, flow + control signals are not explicitly forwarded by the intermediaries. + (However, throttling of data transfer by any recipient may have the + effect of indirectly propagating flow control information upstream + back to the original sender.) Flow control only applies to the data + portion of data frames. Recipients must buffer all control frames. + If a recipient fails to buffer an entire control frame, it MUST issue + a stream error (Section 2.4.2) with the status code + FLOW_CONTROL_ERROR for the stream. + + + +Belshe & Peon Expires August 4, 2012 [Page 22] + +Internet-Draft SPDY Feb 2012 + + + Flow control in SPDY is implemented by a data transfer window kept by + the sender of each stream. The data transfer window is a simple + uint32 that indicates how many bytes of data the sender can transmit. + After a stream is created, but before any data frames have been + transmitted, the sender begins with the initial window size. This + window size is a measure of the buffering capability of the + recipient. The sender must not send a data frame with data length + greater than the transfer window size. After sending each data + frame, the sender decrements its transfer window size by the amount + of data transmitted. When the window size becomes less than or equal + to 0, the sender must pause transmitting data frames. At the other + end of the stream, the recipient sends a WINDOW_UPDATE control back + to notify the sender that it has consumed some data and freed up + buffer space to receive more data. + + +----------------------------------+ + |1| version | 9 | + +----------------------------------+ + | 0 (flags) | 8 (length) | + +----------------------------------+ + |X| Stream-ID (31-bits) | + +----------------------------------+ + |X| Delta-Window-Size (31-bits) | + +----------------------------------+ + + Control bit: The control bit is always 1 for this message. + + Version: The SPDY version number. + + Type: The message type for a WINDOW_UPDATE message is 9. + + Length: The length field is always 8 for this frame (there are 8 + bytes after the length field). + + Stream-ID: The stream ID that this WINDOW_UPDATE control frame is + for. + + Delta-Window-Size: The additional number of bytes that the sender can + transmit in addition to existing remaining window size. The legal + range for this field is 1 to 2^31 - 1 (0x7fffffff) bytes. + + The window size as kept by the sender must never exceed 2^31 + (although it can become negative in one special case). If a sender + receives a WINDOW_UPDATE that causes the its window size to exceed + this limit, it must send RST_STREAM with status code + FLOW_CONTROL_ERROR to terminate the stream. + + When a SPDY connection is first established, the default initial + + + +Belshe & Peon Expires August 4, 2012 [Page 23] + +Internet-Draft SPDY Feb 2012 + + + window size for all streams is 64KB. An endpoint can use the + SETTINGS control frame to adjust the initial window size for the + connection. That is, its peer can start out using the 64KB default + initial window size when sending data frames before receiving the + SETTINGS. Because SETTINGS is asynchronous, there may be a race + condition if the recipient wants to decrease the initial window size, + but its peer immediately sends 64KB on the creation of a new + connection, before waiting for the SETTINGS to arrive. This is one + case where the window size kept by the sender will become negative. + Once the sender detects this condition, it must stop sending data + frames and wait for the recipient to catch up. The recipient has two + choices: + + immediately send RST_STREAM with FLOW_CONTROL_ERROR status code. + + allow the head of line blocking (as there is only one stream for + the session and the amount of data in flight is bounded by the + default initial window size), and send WINDOW_UPDATE as it + consumes data. + + In the case of option 2, both sides must compute the window size + based on the initial window size in the SETTINGS. For example, if + the recipient sets the initial window size to be 16KB, and the sender + sends 64KB immediately on connection establishment, the sender will + discover its window size is -48KB on receipt of the SETTINGS. As the + recipient consumes the first 16KB, it must send a WINDOW_UPDATE of + 16KB back to the sender. This interaction continues until the + sender's window size becomes positive again, and it can resume + transmitting data frames. + + After the recipient reads in a data frame with FLAG_FIN that marks + the end of the data stream, it should not send WINDOW_UPDATE frames + as it consumes the last data frame. A sender should ignore all the + WINDOW_UPDATE frames associated with the stream after it send the + last frame for the stream. + + The data frames from the sender and the WINDOW_UPDATE frames from the + recipient are completely asynchronous with respect to each other. + This property allows a recipient to aggressively update the window + size kept by the sender to prevent the stream from stalling. + +2.6.9. CREDENTIAL + + The CREDENTIAL control frame is used by the client to send additional + client certificates to the server. A SPDY client may decide to send + requests for resources from different origins on the same SPDY + session if it decides that that server handles both origins. For + example if the IP address associated with both hostnames matches and + + + +Belshe & Peon Expires August 4, 2012 [Page 24] + +Internet-Draft SPDY Feb 2012 + + + the SSL server certificate presented in the initial handshake is + valid for both hostnames. However, because the SSL connection can + contain at most one client certificate, the client needs a mechanism + to send additional client certificates to the server. + + The server is required to maintain a vector of client certificates + associated with a SPDY session. When the client needs to send a + client certificate to the server, it will send a CREDENTIAL frame + that specifies the index of the slot in which to store the + certificate as well as proof that the client posesses the + corresponding private key. The initial size of this vector must be + 8. If the client provides a client certificate during the first TLS + handshake, the contents of this certificate must be copied into the + first slot (index 1) in the CREDENTIAL vector, though it may be + overwritten by subsequent CREDENTIAL frames. The server must + exclusively use the CREDNETIAL vector when evaluating the client + certificates associated with an origin. The server may change the + size of this vector by sending a SETTINGS frame with the setting + SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE value specified. In the + event that the new size is smaller than the current size, truncation + occurs preserving lower-index slots as possible. + + TLS renegotiation with client authentication is incompatible with + SPDY given the multiplexed nature of SPDY. Specifically, imagine + that the client has 2 requests outstanding to the server for two + different pages (in different tabs). When the renegotiation + client + certificate request comes in, the browser is unable to determine + which resource triggered the client certificate request, in order to + prompt the user accordingly. + + +----------------------------------+ + |1|000000000000001|0000000000001011| + +----------------------------------+ + | flags (8) | Length (24 bits) | + +----------------------------------+ + | Slot (16 bits) | | + +-----------------+ | + | Proof Length (32 bits) | + +----------------------------------+ + | Proof | + +----------------------------------+ <+ + | Certificate Length (32 bits) | | + +----------------------------------+ | Repeated until end of frame + | Certificate | | + +----------------------------------+ <+ + + Slot: The index in the server's client certificate vector where this + certificate should be stored. If there is already a certificate + + + +Belshe & Peon Expires August 4, 2012 [Page 25] + +Internet-Draft SPDY Feb 2012 + + + stored at this index, it will be overwritten. The index is one + based, not zero based; zero is an invalid slot index. + + Proof: Cryptographic proof that the client has possession of the + private key associated with the certificate. The format is a TLS + digitally-signed element + (http://tools.ietf.org/html/rfc5246#section-4.7). The signature + algorithm must be the same as that used in the CertificateVerify + message. However, since the MD5+SHA1 signature type used in TLS 1.0 + connections can not be correctly encoded in a digitally-signed + element, SHA1 must be used when MD5+SHA1 was used in the SSL + connection. The signature is calculated over a 32 byte TLS extractor + value (http://tools.ietf.org/html/rfc5705) with a label of "EXPORTER + SPDY certificate proof" using the empty string as context. ForRSA + certificates the signature would be a PKCS#1 v1.5 signature. For + ECDSA, it would be an ECDSA-Sig-Value + (http://tools.ietf.org/html/rfc5480#appendix-A). For a 1024-bit RSA + key, the CREDENTIAL message would be ~500 bytes. + + Certificate: The certificate chain, starting with the leaf + certificate. Each certificate must be encoded as a 32 bit length, + followed by a DER encoded certificate. The certificate must be of + the same type (RSA, ECDSA, etc) as the client certificate associated + with the SSL connection. + + If the server receives a request for a resource with unacceptable + credential (either missing or invalid), it must reply with a + RST_STREAM frame with the status code INVALID_CREDENTIALS. Upon + receipt of a RST_STREAM frame with INVALID_CREDENTIALS, the client + should initiate a new stream directly to the requested origin and + resend the request. Note, SPDY does not allow the server to request + different client authentication for different resources in the same + origin. + + If the server receives an invalid CREDENTIAL frame, it MUST respond + with a GOAWAY frame and shutdown the session. + +2.6.10. Name/Value Header Block + + The Name/Value Header Block is found in the SYN_STREAM, SYN_REPLY and + HEADERS control frames, and shares a common format: + + + + + + + + + + +Belshe & Peon Expires August 4, 2012 [Page 26] + +Internet-Draft SPDY Feb 2012 + + + +------------------------------------+ + | Number of Name/Value pairs (int32) | + +------------------------------------+ + | Length of name (int32) | + +------------------------------------+ + | Name (string) | + +------------------------------------+ + | Length of value (int32) | + +------------------------------------+ + | Value (string) | + +------------------------------------+ + | (repeats) | + + Number of Name/Value pairs: The number of repeating name/value pairs + following this field. + + List of Name/Value pairs: + + Length of Name: a 32-bit value containing the number of octets in + the name field. Note that in practice, this length must not + exceed 2^24, as that is the maximum size of a SPDY frame. + + Name: 0 or more octets, 8-bit sequences of data, excluding 0. + + Length of Value: a 32-bit value containing the number of octets in + the value field. Note that in practice, this length must not + exceed 2^24, as that is the maximum size of a SPDY frame. + + Value: 0 or more octets, 8-bit sequences of data, excluding 0. + + Each header name must have at least one value. Header names are + encoded using the US-ASCII character set [ASCII] and must be all + lower case. The length of each name must be greater than zero. A + recipient of a zero-length name MUST issue a stream error + (Section 2.4.2) with the status code PROTOCOL_ERROR for the + stream-id. + + Duplicate header names are not allowed. To send two identically + named headers, send a header with two values, where the values are + separated by a single NUL (0) byte. A header value can either be + empty (e.g. the length is zero) or it can contain multiple, NUL- + separated values, each with length greater than zero. The value + never starts nor ends with a NUL character. Recipients of illegal + value fields MUST issue a stream error (Section 2.4.2) with the + status code PROTOCOL_ERROR for the stream-id. + + + + + + +Belshe & Peon Expires August 4, 2012 [Page 27] + +Internet-Draft SPDY Feb 2012 + + +2.6.10.1. Compression + + The Name/Value Header Block is a section of the SYN_STREAM, + SYN_REPLY, and HEADERS frames used to carry header meta-data. This + block is always compressed using zlib compression. Within this + specification, any reference to 'zlib' is referring to the ZLIB + Compressed Data Format Specification Version 3.3 as part of RFC1950. + [RFC1950] + + For each HEADERS compression instance, the initial state is + initialized using the following dictionary [UDELCOMPRESSION]: + + const unsigned char SPDY_dictionary_txt[] = { + 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, \\ - - - - o p t i + 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, \\ o n s - - - - h + 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, \\ e a d - - - - p + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, \\ o s t - - - - p + 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, \\ u t - - - - d e + 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, \\ l e t e - - - - + 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, \\ t r a c e - - - + 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, \\ - a c c e p t - + 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, \\ - - - a c c e p + 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, \\ t - c h a r s e + 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, \\ t - - - - a c c + 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, \\ e p t - e n c o + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, \\ d i n g - - - - + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, \\ a c c e p t - l + 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, \\ a n g u a g e - + 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, \\ - - - a c c e p + 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, \\ t - r a n g e s + 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, \\ - - - - a g e - + 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, \\ - - - a l l o w + 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, \\ - - - - a u t h + 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, \\ o r i z a t i o + 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, \\ n - - - - c a c + 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, \\ h e - c o n t r + 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, \\ o l - - - - c o + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, \\ n n e c t i o n + 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, \\ - - - - c o n t + 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, \\ e n t - b a s e + 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, \\ - - - - c o n t + 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, \\ e n t - e n c o + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, \\ d i n g - - - - + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, \\ c o n t e n t - + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, \\ l a n g u a g e + 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, \\ - - - - c o n t + 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, \\ e n t - l e n g + 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, \\ t h - - - - c o + + + +Belshe & Peon Expires August 4, 2012 [Page 28] + +Internet-Draft SPDY Feb 2012 + + + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, \\ n t e n t - l o + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, \\ c a t i o n - - + 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, \\ - - c o n t e n + 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, \\ t - m d 5 - - - + 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, \\ - c o n t e n t + 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, \\ - r a n g e - - + 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, \\ - - c o n t e n + 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, \\ t - t y p e - - + 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, \\ - - d a t e - - + 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, \\ - - e t a g - - + 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, \\ - - e x p e c t + 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, \\ - - - - e x p i + 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, \\ r e s - - - - f + 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, \\ r o m - - - - h + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, \\ o s t - - - - i + 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, \\ f - m a t c h - + 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, \\ - - - i f - m o + 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, \\ d i f i e d - s + 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, \\ i n c e - - - - + 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, \\ i f - n o n e - + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, \\ m a t c h - - - + 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, \\ - i f - r a n g + 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, \\ e - - - - i f - + 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, \\ u n m o d i f i + 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, \\ e d - s i n c e + 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, \\ - - - - l a s t + 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, \\ - m o d i f i e + 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, \\ d - - - - l o c + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, \\ a t i o n - - - + 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, \\ - m a x - f o r + 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, \\ w a r d s - - - + 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, \\ - p r a g m a - + 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, \\ - - - p r o x y + 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, \\ - a u t h e n t + 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, \\ i c a t e - - - + 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, \\ - p r o x y - a + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, \\ u t h o r i z a + 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, \\ t i o n - - - - + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, \\ r a n g e - - - + 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, \\ - r e f e r e r + 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, \\ - - - - r e t r + 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, \\ y - a f t e r - + 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, \\ - - - s e r v e + 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, \\ r - - - - t e - + 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, \\ - - - t r a i l + 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, \\ e r - - - - t r + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, \\ a n s f e r - e + 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, \\ n c o d i n g - + + + +Belshe & Peon Expires August 4, 2012 [Page 29] + +Internet-Draft SPDY Feb 2012 + + + 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, \\ - - - u p g r a + 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, \\ d e - - - - u s + 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, \\ e r - a g e n t + 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, \\ - - - - v a r y + 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, \\ - - - - v i a - + 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, \\ - - - w a r n i + 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, \\ n g - - - - w w + 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, \\ w - a u t h e n + 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, \\ t i c a t e - - + 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, \\ - - m e t h o d + 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, \\ - - - - g e t - + 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, \\ - - - s t a t u + 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, \\ s - - - - 2 0 0 + 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, \\ - O K - - - - v + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, \\ e r s i o n - - + 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, \\ - - H T T P - 1 + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, \\ - 1 - - - - u r + 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, \\ l - - - - p u b + 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, \\ l i c - - - - s + 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, \\ e t - c o o k i + 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, \\ e - - - - k e e + 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, \\ p - a l i v e - + 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, \\ - - - o r i g i + 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, \\ n 1 0 0 1 0 1 2 + 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, \\ 0 1 2 0 2 2 0 5 + 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, \\ 2 0 6 3 0 0 3 0 + 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, \\ 2 3 0 3 3 0 4 3 + 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, \\ 0 5 3 0 6 3 0 7 + 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, \\ 4 0 2 4 0 5 4 0 + 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, \\ 6 4 0 7 4 0 8 4 + 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, \\ 0 9 4 1 0 4 1 1 + 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, \\ 4 1 2 4 1 3 4 1 + 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, \\ 4 4 1 5 4 1 6 4 + 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, \\ 1 7 5 0 2 5 0 4 + 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, \\ 5 0 5 2 0 3 - N + 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, \\ o n - A u t h o + 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, \\ r i t a t i v e + 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, \\ - I n f o r m a + 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, \\ t i o n 2 0 4 - + 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, \\ N o - C o n t e + 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, \\ n t 3 0 1 - M o + 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, \\ v e d - P e r m + 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, \\ a n e n t l y 4 + 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, \\ 0 0 - B a d - R + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, \\ e q u e s t 4 0 + 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, \\ 1 - U n a u t h + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, \\ o r i z e d 4 0 + 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, \\ 3 - F o r b i d + + + +Belshe & Peon Expires August 4, 2012 [Page 30] + +Internet-Draft SPDY Feb 2012 + + + 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, \\ d e n 4 0 4 - N + 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, \\ o t - F o u n d + 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, \\ 5 0 0 - I n t e + 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, \\ r n a l - S e r + 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, \\ v e r - E r r o + 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, \\ r 5 0 1 - N o t + 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, \\ - I m p l e m e + 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, \\ n t e d 5 0 3 - + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, \\ S e r v i c e - + 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, \\ U n a v a i l a + 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, \\ b l e J a n - F + 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, \\ e b - M a r - A + 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, \\ p r - M a y - J + 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, \\ u n - J u l - A + 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, \\ u g - S e p t - + 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, \\ O c t - N o v - + 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, \\ D e c - 0 0 - 0 + 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, \\ 0 - 0 0 - M o n + 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, \\ - - T u e - - W + 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, \\ e d - - T h u - + 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, \\ - F r i - - S a + 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, \\ t - - S u n - - + 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, \\ G M T c h u n k + 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, \\ e d - t e x t - + 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, \\ h t m l - i m a + 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, \\ g e - p n g - i + 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, \\ m a g e - j p g + 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, \\ - i m a g e - g + 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, \\ i f - a p p l i + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, \\ c a t i o n - x + 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, \\ m l - a p p l i + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, \\ c a t i o n - x + 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, \\ h t m l - x m l + 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, \\ - t e x t - p l + 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, \\ a i n - t e x t + 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, \\ - j a v a s c r + 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, \\ i p t - p u b l + 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, \\ i c p r i v a t + 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, \\ e m a x - a g e + 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, \\ - g z i p - d e + 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, \\ f l a t e - s d + 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, \\ c h c h a r s e + 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, \\ t - u t f - 8 c + 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, \\ h a r s e t - i + 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, \\ s o - 8 8 5 9 - + 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, \\ 1 - u t f - - - + 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e \\ - e n q - 0 - + }; + + + +Belshe & Peon Expires August 4, 2012 [Page 31] + +Internet-Draft SPDY Feb 2012 + + + The entire contents of the name/value header block is compressed + using zlib. There is a single zlib stream for all name value pairs + in one direction on a connection. SPDY uses a SYNC_FLUSH between + each compressed frame. + + Implementation notes: the compression engine can be tuned to favor + speed or size. Optimizing for size increases memory use and CPU + consumption. Because header blocks are generally small, implementors + may want to reduce the window-size of the compression engine from the + default 15bits (a 32KB window) to more like 11bits (a 2KB window). + The exact setting is chosen by the compressor, the decompressor will + work with any setting. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Belshe & Peon Expires August 4, 2012 [Page 32] + +Internet-Draft SPDY Feb 2012 + + +3. HTTP Layering over SPDY + + SPDY is intended to be as compatible as possible with current web- + based applications. This means that, from the perspective of the + server business logic or application API, the features of HTTP are + unchanged. To achieve this, all of the application request and + response header semantics are preserved, although the syntax of + conveying those semantics has changed. Thus, the rules from the + HTTP/1.1 specification in RFC2616 [RFC2616] apply with the changes in + the sections below. + +3.1. Connection Management + + Clients SHOULD NOT open more than one SPDY session to a given origin + [RFC6454] concurrently. + + Note that it is possible for one SPDY session to be finishing (e.g. a + GOAWAY message has been sent, but not all streams have finished), + while another SPDY session is starting. + +3.1.1. Use of GOAWAY + + SPDY provides a GOAWAY message which can be used when closing a + connection from either the client or server. Without a server GOAWAY + message, HTTP has a race condition where the client sends a request + (a new SYN_STREAM) just as the server is closing the connection, and + the client cannot know if the server received the stream or not. By + using the last-stream-id in the GOAWAY, servers can indicate to the + client if a request was processed or not. + + Note that some servers will choose to send the GOAWAY and immediately + terminate the connection without waiting for active streams to + finish. The client will be able to determine this because SPDY + streams are determinstically closed. This abrupt termination will + force the client to heuristically decide whether to retry the pending + requests. Clients always need to be capable of dealing with this + case because they must deal with accidental connection termination + cases, which are the same as the server never having sent a GOAWAY. + + More sophisticated servers will use GOAWAY to implement a graceful + teardown. They will send the GOAWAY and provide some time for the + active streams to finish before terminating the connection. + + If a SPDY client closes the connection, it should also send a GOAWAY + message. This allows the server to know if any server-push streams + were received by the client. + + If the endpoint closing the connection has not received any + + + +Belshe & Peon Expires August 4, 2012 [Page 33] + +Internet-Draft SPDY Feb 2012 + + + SYN_STREAMs from the remote, the GOAWAY will contain a last-stream-id + of 0. + +3.2. HTTP Request/Response + +3.2.1. Request + + The client initiates a request by sending a SYN_STREAM frame. For + requests which do not contain a body, the SYN_STREAM frame MUST set + the FLAG_FIN, indicating that the client intends to send no further + data on this stream. For requests which do contain a body, the + SYN_STREAM will not contain the FLAG_FIN, and the body will follow + the SYN_STREAM in a series of DATA frames. The last DATA frame will + set the FLAG_FIN to indicate the end of the body. + + The SYN_STREAM Name/Value section will contain all of the HTTP + headers which are associated with an HTTP request. The header block + in SPDY is mostly unchanged from today's HTTP header block, with the + following differences: + + The first line of the request is unfolded into name/value pairs + like other HTTP headers and MUST be present: + + ":method" - the HTTP method for this request (e.g. "GET", + "POST", "HEAD", etc) + + ":path" - the url-path for this url with "/" prefixed. (See + RFC1738 [RFC1738]). For example, for + "http://www.google.com/search?q=dogs" the path would be + "/search?q=dogs". + + ":version" - the HTTP version of this request (e.g. + "HTTP/1.1") + + In addition, the following two name/value pairs must also be + present in every request: + + ":host" - the hostport (See RFC1738 [RFC1738]) portion of the + URL for this request (e.g. "www.google.com:1234"). This header + is the same as the HTTP 'Host' header. + + ":scheme" - the scheme portion of the URL for this request + (e.g. "https")) + + Header names are all lowercase. + + The Connection, Host, Keep-Alive, Proxy-Connection, and Transfer- + Encoding headers are not valid and MUST not be sent. + + + +Belshe & Peon Expires August 4, 2012 [Page 34] + +Internet-Draft SPDY Feb 2012 + + + User-agents MUST support gzip compression. Regardless of the + Accept-Encoding sent by the user-agent, the server may always send + content encoded with gzip or deflate encoding. + + If a server receives a request where the sum of the data frame + payload lengths does not equal the size of the Content-Length + header, the server MUST return a 400 (Bad Request) error. + + POST-specific changes: + + Although POSTs are inherently chunked, POST requests SHOULD + also be accompanied by a Content-Length header. There are two + reasons for this: First, it assists with upload progress meters + for an improved user experience. But second, we know from + early versions of SPDY that failure to send a content length + header is incompatible with many existing HTTP server + implementations. Existing user-agents do not omit the Content- + Length header, and server implementations have come to depend + upon this. + + The user-agent is free to prioritize requests as it sees fit. If the + user-agent cannot make progress without receiving a resource, it + should attempt to raise the priority of that resource. Resources + such as images, SHOULD generally use the lowest priority. + + If a client sends a SYN_STREAM without all of the method, host, path, + scheme, and version headers, the server MUST reply with a HTTP 400 + Bad Request reply. + +3.2.2. Response + + The server responds to a client request with a SYN_REPLY frame. + Symmetric to the client's upload stream, server will send data after + the SYN_REPLY frame via a series of DATA frames, and the last data + frame will contain the FLAG_FIN to indicate successful end-of-stream. + If a response (like a 202 or 204 response) contains no body, the + SYN_REPLY frame may contain the FLAG_FIN flag to indicate no further + data will be sent on the stream. + + The response status line is unfolded into name/value pairs like + other HTTP headers and must be present: + + ":status" - The HTTP response status code (e.g. "200" or "200 + OK") + + ":version" - The HTTP response version (e.g. "HTTP/1.1") + + + + + +Belshe & Peon Expires August 4, 2012 [Page 35] + +Internet-Draft SPDY Feb 2012 + + + All header names must be lowercase. + + The Connection, Keep-Alive, Proxy-Connection, and Transfer- + Encoding headers are not valid and MUST not be sent. + + Responses MAY be accompanied by a Content-Length header for + advisory purposes. (e.g. for UI progress meters) + + If a client receives a response where the sum of the data frame + payload lengths does not equal the size of the Content-Length + header, the client MUST ignore the content length header. + + If a client receives a SYN_REPLY without a status or without a + version header, the client must reply with a RST_STREAM frame + indicating a PROTOCOL ERROR. + +3.2.3. Authentication + + When a client sends a request to an origin server that requires + authentication, the server can reply with a "401 Unauthorized" + response, and include a WWW-Authenticate challenge header that + defines the authentication scheme to be used. The client then + retries the request with an Authorization header appropriate to the + specified authentication scheme. + + There are four options for proxy authentication, Basic, Digest, NTLM + and Negotiate (SPNEGO). The first two options were defined in + RFC2617 [RFC2617], and are stateless. The second two options were + developed by Microsoft and specified in RFC4559 [RFC4559], and are + stateful; otherwise known as multi-round authentication, or + connection authentication. + +3.2.3.1. Stateless Authentication + + Stateless Authentication over SPDY is identical to how it is + performed over HTTP. If multiple SPDY streams are concurrently sent + to a single server, each will authenticate independently, similar to + how two HTTP connections would independently authenticate to a proxy + server. + +3.2.3.2. Stateful Authentication + + Unfortunately, the stateful authentication mechanisms were + implemented and defined in a such a way that directly violates + RFC2617 - they do not include a "realm" as part of the request. This + is problematic in SPDY because it makes it impossible for a client to + disambiguate two concurrent server authentication challenges. + + + + +Belshe & Peon Expires August 4, 2012 [Page 36] + +Internet-Draft SPDY Feb 2012 + + + To deal with this case, SPDY servers using Stateful Authentication + MUST implement one of two changes: + + Servers can add a "realm=<desired realm>" header so that the two + authentication requests can be disambiguated and run concurrently. + Unfortunately, given how these mechanisms work, this is probably + not practical. + + Upon sending the first stateful challenge response, the server + MUST buffer and defer all further frames which are not part of + completing the challenge until the challenge has completed. + Completing the authentication challenge may take multiple round + trips. Once the client receives a "401 Authenticate" response for + a stateful authentication type, it MUST stop sending new requests + to the server until the authentication has completed by receiving + a non-401 response on at least one stream. + +3.3. Server Push Transactions + + SPDY enables a server to send multiple replies to a client for a + single request. The rationale for this feature is that sometimes a + server knows that it will need to send multiple resources in response + to a single request. Without server push features, the client must + first download the primary resource, then discover the secondary + resource(s), and request them. Pushing of resources avoids the + round-trip delay, but also creates a potential race where a server + can be pushing content which a user-agent is in the process of + requesting. The following mechanics attempt to prevent the race + condition while enabling the performance benefit. + + Browsers receiving a pushed response MUST validate that the server is + authorized to push the URL using the browser same-origin [RFC6454] + policy. For example, a SPDY connection to www.foo.com is generally + not permitted to push a response for www.evil.com. + + If the browser accepts a pushed response (e.g. it does not send a + RST_STREAM), the browser MUST attempt to cache the pushed response in + same way that it would cache any other response. This means + validating the response headers and inserting into the disk cache. + + Because pushed responses have no request, they have no request + headers associated with them. At the framing layer, SPDY pushed + streams contain an "associated-stream-id" which indicates the + requested stream for which the pushed stream is related. The pushed + stream inherits all of the headers from the associated-stream-id with + the exception of ":host", ":scheme", and ":path", which are provided + as part of the pushed response stream headers. The browser MUST + store these inherited and implied request headers with the cached + + + +Belshe & Peon Expires August 4, 2012 [Page 37] + +Internet-Draft SPDY Feb 2012 + + + resource. + + Implementation note: With server push, it is theoretically possible + for servers to push unreasonable amounts of content or resources to + the user-agent. Browsers MUST implement throttles to protect against + unreasonable push attacks. + +3.3.1. Server implementation + + When the server intends to push a resource to the user-agent, it + opens a new stream by sending a unidirectional SYN_STREAM. The + SYN_STREAM MUST include an Associated-To-Stream-ID, and MUST set the + FLAG_UNIDIRECTIONAL flag. The SYN_STREAM MUST include headers for + ":scheme", ":host", ":path", which represent the URL for the resource + being pushed. Subsequent headers may follow in HEADERS frames. The + purpose of the association is so that the user-agent can + differentiate which request induced the pushed stream; without it, if + the user-agent had two tabs open to the same page, each pushing + unique content under a fixed URL, the user-agent would not be able to + differentiate the requests. + + The Associated-To-Stream-ID must be the ID of an existing, open + stream. The reason for this restriction is to have a clear endpoint + for pushed content. If the user-agent requested a resource on stream + 11, the server replies on stream 11. It can push any number of + additional streams to the client before sending a FLAG_FIN on stream + 11. However, once the originating stream is closed no further push + streams may be associated with it. The pushed streams do not need to + be closed (FIN set) before the originating stream is closed, they + only need to be created before the originating stream closes. + + It is illegal for a server to push a resource with the Associated-To- + Stream-ID of 0. + + To minimize race conditions with the client, the SYN_STREAM for the + pushed resources MUST be sent prior to sending any content which + could allow the client to discover the pushed resource and request + it. + + The server MUST only push resources which would have been returned + from a GET request. + + Note: If the server does not have all of the Name/Value Response + headers available at the time it issues the HEADERS frame for the + pushed resource, it may later use an additional HEADERS frame to + augment the name/value pairs to be associated with the pushed stream. + The subsequent HEADERS frame(s) must not contain a header for + ':host', ':scheme', or ':path' (e.g. the server can't change the + + + +Belshe & Peon Expires August 4, 2012 [Page 38] + +Internet-Draft SPDY Feb 2012 + + + identity of the resource to be pushed). The HEADERS frame must not + contain duplicate headers with a previously sent HEADERS frame. The + server must send a HEADERS frame including the scheme/host/port + headers before sending any data frames on the stream. + +3.3.2. Client implementation + + When fetching a resource the client has 3 possibilities: + + the resource is not being pushed + + the resource is being pushed, but the data has not yet arrived + + the resource is being pushed, and the data has started to arrive + + When a SYN_STREAM and HEADERS frame which contains an Associated-To- + Stream-ID is received, the client must not issue GET requests for the + resource in the pushed stream, and instead wait for the pushed stream + to arrive. + + If a client receives a server push stream with stream-id 0, it MUST + issue a session error (Section 2.4.1) with the status code + PROTOCOL_ERROR. + + When a client receives a SYN_STREAM from the server without a the + ':host', ':scheme', and ':path' headers in the Name/Value section, it + MUST reply with a RST_STREAM with error code HTTP_PROTOCOL_ERROR. + + To cancel individual server push streams, the client can issue a + stream error (Section 2.4.2) with error code CANCEL. Upon receipt, + the server MUST stop sending on this stream immediately (this is an + Abrupt termination). + + To cancel all server push streams related to a request, the client + may issue a stream error (Section 2.4.2) with error code CANCEL on + the associated-stream-id. By cancelling that stream, the server MUST + immediately stop sending frames for any streams with + in-association-to for the original stream. + + If the server sends a HEADER frame containing duplicate headers with + a previous HEADERS frame for the same stream, the client must issue a + stream error (Section 2.4.2) with error code PROTOCOL ERROR. + + If the server sends a HEADERS frame after sending a data frame for + the same stream, the client MAY ignore the HEADERS frame. Ignoring + the HEADERS frame after a data frame prevents handling of HTTP's + trailing headers + (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.40). + + + +Belshe & Peon Expires August 4, 2012 [Page 39] + +Internet-Draft SPDY Feb 2012 + + +4. Design Rationale and Notes + + Authors' notes: The notes in this section have no bearing on the SPDY + protocol as specified within this document, and none of these notes + should be considered authoritative about how the protocol works. + However, these notes may prove useful in future debates about how to + resolve protocol ambiguities or how to evolve the protocol going + forward. They may be removed before the final draft. + +4.1. Separation of Framing Layer and Application Layer + + Readers may note that this specification sometimes blends the framing + layer (Section 2) with requirements of a specific application - HTTP + (Section 3). This is reflected in the request/response nature of the + streams, the definition of the HEADERS and compression contexts which + are very similar to HTTP, and other areas as well. + + This blending is intentional - the primary goal of this protocol is + to create a low-latency protocol for use with HTTP. Isolating the + two layers is convenient for description of the protocol and how it + relates to existing HTTP implementations. However, the ability to + reuse the SPDY framing layer is a non goal. + +4.2. Error handling - Framing Layer + + Error handling at the SPDY layer splits errors into two groups: Those + that affect an individual SPDY stream, and those that do not. + + When an error is confined to a single stream, but general framing is + in tact, SPDY attempts to use the RST_STREAM as a mechanism to + invalidate the stream but move forward without aborting the + connection altogether. + + For errors occuring outside of a single stream context, SPDY assumes + the entire session is hosed. In this case, the endpoint detecting + the error should initiate a connection close. + +4.3. One Connection Per Domain + + SPDY attempts to use fewer connections than other protocols have + traditionally used. The rationale for this behavior is because it is + very difficult to provide a consistent level of service (e.g. TCP + slow-start), prioritization, or optimal compression when the client + is connecting to the server through multiple channels. + + Through lab measurements, we have seen consistent latency benefits by + using fewer connections from the client. The overall number of + packets sent by SPDY can be as much as 40% less than HTTP. Handling + + + +Belshe & Peon Expires August 4, 2012 [Page 40] + +Internet-Draft SPDY Feb 2012 + + + large numbers of concurrent connections on the server also does + become a scalability problem, and SPDY reduces this load. + + The use of multiple connections is not without benefit, however. + Because SPDY multiplexes multiple, independent streams onto a single + stream, it creates a potential for head-of-line blocking problems at + the transport level. In tests so far, the negative effects of head- + of-line blocking (especially in the presence of packet loss) is + outweighed by the benefits of compression and prioritization. + +4.4. Fixed vs Variable Length Fields + + SPDY favors use of fixed length 32bit fields in cases where smaller, + variable length encodings could have been used. To some, this seems + like a tragic waste of bandwidth. SPDY choses the simple encoding + for speed and simplicity. + + The goal of SPDY is to reduce latency on the network. The overhead + of SPDY frames is generally quite low. Each data frame is only an 8 + byte overhead for a 1452 byte payload (~0.6%). At the time of this + writing, bandwidth is already plentiful, and there is a strong trend + indicating that bandwidth will continue to increase. With an average + worldwide bandwidth of 1Mbps, and assuming that a variable length + encoding could reduce the overhead by 50%, the latency saved by using + a variable length encoding would be less than 100 nanoseconds. More + interesting are the effects when the larger encodings force a packet + boundary, in which case a round-trip could be induced. However, by + addressing other aspects of SPDY and TCP interactions, we believe + this is completely mitigated. + +4.5. Compression Context(s) + + When isolating the compression contexts used for communicating with + multiple origins, we had a few choices to make. We could have + maintained a map (or list) of compression contexts usable for each + origin. The basic case is easy - each HEADERS frame would need to + identify the context to use for that frame. However, compression + contexts are not cheap, so the lifecycle of each context would need + to be bounded. For proxy servers, where we could churn through many + contexts, this would be a concern. We considered using a static set + of contexts, say 16 of them, which would bound the memory use. We + also considered dynamic contexts, which could be created on the fly, + and would need to be subsequently destroyed. All of these are + complicated, and ultimately we decided that such a mechanism creates + too many problems to solve. + + Alternatively, we've chosen the simple approach, which is to simply + provide a flag for resetting the compression context. For the common + + + +Belshe & Peon Expires August 4, 2012 [Page 41] + +Internet-Draft SPDY Feb 2012 + + + case (no proxy), this fine because most requests are to the same + origin and we never need to reset the context. For cases where we + are using two different origins over a single SPDY session, we simply + reset the compression state between each transition. + +4.6. Unidirectional streams + + Many readers notice that unidirectional streams are both a bit + confusing in concept and also somewhat redundant. If the recipient + of a stream doesn't wish to send data on a stream, it could simply + send a SYN_REPLY with the FLAG_FIN bit set. The FLAG_UNIDIRECTIONAL + is, therefore, not necessary. + + It is true that we don't need the UNIDIRECTIONAL markings. It is + added because it avoids the recipient of pushed streams from needing + to send a set of empty frames (e.g. the SYN_STREAM w/ FLAG_FIN) which + otherwise serve no purpose. + +4.7. Data Compression + + Generic compression of data portion of the streams (as opposed to + compression of the headers) without knowing the content of the stream + is redundant. There is no value in compressing a stream which is + already compressed. Because of this, SPDY does allow data + compression to be optional. We included it because study of existing + websites shows that many sites are not using compression as they + should, and users suffer because of it. We wanted a mechanism where, + at the SPDY layer, site administrators could simply force compression + - it is better to compress twice than to not compress. + + Overall, however, with this feature being optional and sometimes + redundant, it is unclear if it is useful at all. We will likely + remove it from the specification. + +4.8. Server Push + + A subtle but important point is that server push streams must be + declared before the associated stream is closed. The reason for this + is so that proxies have a lifetime for which they can discard + information about previous streams. If a pushed stream could + associate itself with an already-closed stream, then endpoints would + not have a specific lifecycle for when they could disavow knowledge + of the streams which went before. + + + + + + + + +Belshe & Peon Expires August 4, 2012 [Page 42] + +Internet-Draft SPDY Feb 2012 + + +5. Security Considerations + +5.1. Use of Same-origin constraints + + This specification uses the same-origin policy [RFC6454] in all cases + where verification of content is required. + +5.2. HTTP Headers and SPDY Headers + + At the application level, HTTP uses name/value pairs in its headers. + Because SPDY merges the existing HTTP headers with SPDY headers, + there is a possibility that some HTTP applications already use a + particular header name. To avoid any conflicts, all headers + introduced for layering HTTP over SPDY are prefixed with ":". ":" is + not a valid sequence in HTTP header naming, preventing any possible + conflict. + +5.3. Cross-Protocol Attacks + + By utilizing TLS, we believe that SPDY introduces no new cross- + protocol attacks. TLS encrypts the contents of all transmission + (except the handshake itself), making it difficult for attackers to + control the data which could be used in a cross-protocol attack. + +5.4. Server Push Implicit Headers + + Pushed resources do not have an associated request. In order for + existing HTTP cache control validations (such as the Vary header) to + work, however, all cached resources must have a set of request + headers. For this reason, browsers MUST be careful to inherit + request headers from the associated stream for the push. This + includes the 'Cookie' header. + + + + + + + + + + + + + + + + + + + +Belshe & Peon Expires August 4, 2012 [Page 43] + +Internet-Draft SPDY Feb 2012 + + +6. Privacy Considerations + +6.1. Long Lived Connections + + SPDY aims to keep connections open longer between clients and servers + in order to reduce the latency when a user makes a request. The + maintenance of these connections over time could be used to expose + private information. For example, a user using a browser hours after + the previous user stopped using that browser may be able to learn + about what the previous user was doing. This is a problem with HTTP + in its current form as well, however the short lived connections make + it less of a risk. + +6.2. SETTINGS frame + + The SPDY SETTINGS frame allows servers to store out-of-band + transmitted information about the communication between client and + server on the client. Although this is intended only to be used to + reduce latency, renegade servers could use it as a mechanism to store + identifying information about the client in future requests. + + Clients implementing privacy modes, such as Google Chrome's + "incognito mode", may wish to disable client-persisted SETTINGS + storage. + + Clients MUST clear persisted SETTINGS information when clearing the + cookies. + + TODO: Put range maximums on each type of setting to limit + inappropriate uses. + + + + + + + + + + + + + + + + + + + + + +Belshe & Peon Expires August 4, 2012 [Page 44] + +Internet-Draft SPDY Feb 2012 + + +7. Incompatibilities with SPDY draft #2 + + Here is a list of the major changes between this draft and draft #2. + + Addition of flow control + + Increased 16 bit length fields in SYN_STREAM and SYN_REPLY to 32 + bits. + + Changed definition of compression for DATA frames + + Updated compression dictionary + + Fixed off-by-one on the compression dictionary for headers + + Increased priority field from 2bits to 3bits. + + Removed NOOP frame + + Split the request "url" into "scheme", "host", and "path" + + Added the requirement that POSTs contain content-length. + + Removed wasted 16bits of unused space from the end of the + SYN_REPLY and HEADERS frames. + + Fixed bug: Priorities were described backward (0 was lowest + instead of highest). + + Fixed bug: Name/Value header counts were duplicated in both the + Name Value header block and also the containing frame. + + + + + + + + + + + + + + + + + + + + +Belshe & Peon Expires August 4, 2012 [Page 45] + +Internet-Draft SPDY Feb 2012 + + +8. Requirements Notation + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in RFC 2119 [RFC2119]. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Belshe & Peon Expires August 4, 2012 [Page 46] + +Internet-Draft SPDY Feb 2012 + + +9. Acknowledgements + + Many individuals have contributed to the design and evolution of + SPDY: Adam Langley, Wan-Teh Chang, Jim Morrison, Mark Nottingham, + Alyssa Wilk, Costin Manolache, William Chan, Vitaliy Lvin, Joe Chan, + Adam Barth, Ryan Hamilton, Gavin Peters, Kent Alstad, Kevin Lindsay, + Paul Amer, Fan Yang, Jonathan Leighton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Belshe & Peon Expires August 4, 2012 [Page 47] + +Internet-Draft SPDY Feb 2012 + + +10. Normative References + + [RFC0793] Postel, J., "Transmission Control Protocol", STD 7, + RFC 793, September 1981. + + [RFC1738] Berners-Lee, T., Masinter, L., and M. McCahill, "Uniform + Resource Locators (URL)", RFC 1738, December 1994. + + [RFC1950] Deutsch, L. and J-L. Gailly, "ZLIB Compressed Data Format + Specification version 3.3", RFC 1950, May 1996. + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC2285] Mandeville, R., "Benchmarking Terminology for LAN + Switching Devices", RFC 2285, February 1998. + + [RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H., + Masinter, L., Leach, P., and T. Berners-Lee, "Hypertext + Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999. + + [RFC2617] Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., + Leach, P., Luotonen, A., and L. Stewart, "HTTP + Authentication: Basic and Digest Access Authentication", + RFC 2617, June 1999. + + [RFC4559] Jaganathan, K., Zhu, L., and J. Brezak, "SPNEGO-based + Kerberos and NTLM HTTP Authentication in Microsoft + Windows", RFC 4559, June 2006. + + [RFC4366] Blake-Wilson, S., Nystrom, M., Hopwood, D., Mikkelsen, J., + and T. Wright, "Transport Layer Security (TLS) + Extensions", RFC 4366, April 2006. + + [RFC5246] Dierks, T. and E. Rescorla, "The Transport Layer Security + (TLS) Protocol Version 1.2", RFC 5246, August 2008. + + [RFC6454] Barth, A., "The Web Origin Concept", RFC 6454, + December 2011. + + [TLSNPN] Langley, A., "TLS Next Protocol Negotiation", + <http://tools.ietf.org/html/ + draft-agl-tls-nextprotoneg-01>. + + [ASCII] "US-ASCII. Coded Character Set - 7-Bit American Standard + Code for Information Interchange. Standard ANSI X3.4-1986, + ANSI, 1986.". + + + + +Belshe & Peon Expires August 4, 2012 [Page 48] + +Internet-Draft SPDY Feb 2012 + + + [UDELCOMPRESSION] + Yang, F., Amer, P., and J. Leighton, "A Methodology to + Derive SPDY's Initial Dictionary for Zlib Compression", + <http://www.eecis.udel.edu/~amer/PEL/poc/pdf/ + SPDY-Fan.pdf>. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Belshe & Peon Expires August 4, 2012 [Page 49] + +Internet-Draft SPDY Feb 2012 + + +Appendix A. Changes + + To be removed by RFC Editor before publication + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Belshe & Peon Expires August 4, 2012 [Page 50] + +Internet-Draft SPDY Feb 2012 + + +Authors' Addresses + + Mike Belshe + Twist + + Email: mbelshe@chromium.org + + + Roberto Peon + Google, Inc + + Email: fenix@google.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Belshe & Peon Expires August 4, 2012 [Page 51] + diff --git a/libmicrospdy.pc.in b/libmicrospdy.pc.in @@ -0,0 +1,13 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libmicrospdy +Description: A library for creating an embedded SPDY server +Version: @VERSION@ +Requires: +Conflicts: +Libs: -L${libdir} -lmicrospdy +Libs.private: @SPDY_LIBDEPS@ +Cflags: -I${includedir} diff --git a/m4/openssl.m4 b/m4/openssl.m4 @@ -0,0 +1,69 @@ +dnl Check to find the OpenSSL headers/libraries + +AC_DEFUN([spdy_OPENSSL], +[ + AC_ARG_WITH(openssl, + AS_HELP_STRING([--with-openssl=DIR], [OpenSSL base directory, or:]), + [openssl="$withval" + CPPFLAGS="$CPPFLAGS -I$withval/include" + LDFLAGS="$LDFLAGS -L$withval/lib"] + ) + + AC_ARG_WITH(openssl-include, + AS_HELP_STRING([--with-openssl-include=DIR], [OpenSSL headers directory (without trailing /openssl)]), + [openssl_include="$withval" + CPPFLAGS="$CPPFLAGS -I$withval"] + ) + + AC_ARG_WITH(openssl-lib, + AS_HELP_STRING([--with-openssl-lib=DIR], [OpenSSL library directory]), + [openssl_lib="$withval" + LDFLAGS="$LDFLAGS -L$withval"] + ) + + AC_CHECK_HEADERS(openssl/evp.h openssl/rsa.h openssl/rand.h openssl/err.h openssl/sha.h openssl/pem.h openssl/engine.h, + [], + [AC_MSG_WARN([OpenSSL header files not found.]); break] + ) + +case $host_os in + *mingw*) + AC_CHECK_LIB(crypto, SHA1_Init, + [LIBS="$LIBS -lcrypto -lgdi32"], + [AC_MSG_WARN([OpenSSL libraries not found.])] + ) + ;; + *) + + AC_CHECK_LIB(crypto, SHA1_Init, + [LIBS="$LIBS -lcrypto"], + [AC_MSG_WARN([OpenSSL libraries not found.])] + ) + + AC_CHECK_FUNC(dlopen, + [], + [AC_CHECK_LIB(dl, dlopen, + [LIBS="$LIBS -ldl"], + [AC_MSG_WARN([OpenSSL depends on libdl.]); break] + )] + ) + + AC_CHECK_FUNC(SSL_library_init, + [], + [AC_CHECK_LIB(ssl, SSL_library_init, + [LIBS="$LIBS -lssl"], + [AC_MSG_WARN([OpenSSL?.]); break] + )] + ) + ;; +esac + +# AC_CHECK_FUNCS([RAND_pseudo_bytes EVP_EncryptInit_ex], , +# [AC_MSG_ERROR([Missing OpenSSL functionality, make sure you have installed the latest version.]); break], +# ) + +# AC_CHECK_DECL([OpenSSL_add_all_algorithms], , +# [AC_MSG_ERROR([Missing OpenSSL functionality, make sure you have installed the latest version.]); break], +# [#include <openssl/evp.h>] +# ) +]) diff --git a/src/Makefile.am b/src/Makefile.am @@ -6,4 +6,13 @@ zzuftests = testzzuf endif endif endif -SUBDIRS = include microhttpd examples $(curltests) $(zzuftests) . +if ENABLE_SPDY +# fixme: should test for spdylay before building testspdy! +microspdy = microspdy testspdy +endif + +SUBDIRS = include microhttpd $(microspdy) examples $(curltests) $(zzuftests) . + +EXTRA_DIST = \ + datadir/cert-and-key.pem \ + datadir/cert-and-key-for-wireshark.pem diff --git a/src/datadir/cert-and-key-for-wireshark.pem b/src/datadir/cert-and-key-for-wireshark.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDc1k7EFEspRcr6PdPmvAd02hBDUG2O5dDkoRK+6tgEBvQxsTxz +50TGwJ8RbSV+qUOnncZBwhnI4i71QSEezMP6I6liRA+fUtdh3cZFvdDpxgU6P15y +5JxfnnDeZJR5O4tfMxN99t34EOEMruZZ0CNYJJgbmIteE0hLI418oUs7cwIDAQAB +AoGAW3WOLXrSHge/pp/QkLCyzdw5/AblONdJCkcDQnp0eEaA/8uNY9sWCtJfjpIL +g0eKs3KOV1GR6DZ0iDIvC1h2mO6pwyrJhRYHKPO9pnx7xpv1T9zYTuVwoMfVjPfO +UCWFedSsSKR76+oP0TrwPDqp3JoMFcAyAqZKMg2JrRUpL3ECQQD92cVSYxSEKwX3 +cHWXVp1mSkuRMR/KX70NC/9XpYr8FEjvtXBTkmp7oG3TaDwd6nhSAodtY+Zkseyj +k5NXM6sNAkEA3rT6opc1YK0pL3uweg+AFP4lRUYrrft1bDELhLmVm9Nf4xEdTnDO +IotiX4GYQ64Bm8J+yxVU204soGynVXbgfwJBALQLCqq+X0TGhvrSpnRqGET+mM4n +u1Z7xMhGJBpz7TmQ4ZIya7K6fA+m3347xbeqHyB7brYlTrlIgIAcITqOCNkCQQDE +3tuJC34mHi0ASqkw3a7t39R2rpdCT733jEuQYrY8b9id061CgDnZE7o8j0VY3uOR +G5gWUp8W1r5gemxaAqJlAkEAwVImBKyKy3oIMsd3WsoqYCMBM+cpX8H2JAEEISPT +Y5zSPZ7kT9JgY2zJwXf3qL+uG1oKZ6f0wn4BYujctTbXwQ== +-----END RSA PRIVATE KEY----- diff --git a/src/datadir/cert-and-key.pem b/src/datadir/cert-and-key.pem @@ -0,0 +1,34 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDc1k7EFEspRcr6PdPmvAd02hBDUG2O5dDkoRK+6tgEBvQxsTxz +50TGwJ8RbSV+qUOnncZBwhnI4i71QSEezMP6I6liRA+fUtdh3cZFvdDpxgU6P15y +5JxfnnDeZJR5O4tfMxN99t34EOEMruZZ0CNYJJgbmIteE0hLI418oUs7cwIDAQAB +AoGAW3WOLXrSHge/pp/QkLCyzdw5/AblONdJCkcDQnp0eEaA/8uNY9sWCtJfjpIL +g0eKs3KOV1GR6DZ0iDIvC1h2mO6pwyrJhRYHKPO9pnx7xpv1T9zYTuVwoMfVjPfO +UCWFedSsSKR76+oP0TrwPDqp3JoMFcAyAqZKMg2JrRUpL3ECQQD92cVSYxSEKwX3 +cHWXVp1mSkuRMR/KX70NC/9XpYr8FEjvtXBTkmp7oG3TaDwd6nhSAodtY+Zkseyj +k5NXM6sNAkEA3rT6opc1YK0pL3uweg+AFP4lRUYrrft1bDELhLmVm9Nf4xEdTnDO +IotiX4GYQ64Bm8J+yxVU204soGynVXbgfwJBALQLCqq+X0TGhvrSpnRqGET+mM4n +u1Z7xMhGJBpz7TmQ4ZIya7K6fA+m3347xbeqHyB7brYlTrlIgIAcITqOCNkCQQDE +3tuJC34mHi0ASqkw3a7t39R2rpdCT733jEuQYrY8b9id061CgDnZE7o8j0VY3uOR +G5gWUp8W1r5gemxaAqJlAkEAwVImBKyKy3oIMsd3WsoqYCMBM+cpX8H2JAEEISPT +Y5zSPZ7kT9JgY2zJwXf3qL+uG1oKZ6f0wn4BYujctTbXwQ== +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDJTCCAo6gAwIBAgIJAIjfJkuxM1pAMA0GCSqGSIb3DQEBBQUAMGsxCzAJBgNV +BAYTAkJHMQ4wDAYDVQQIEwVTb2ZpYTEOMAwGA1UEBxMFU29maWExCzAJBgNVBAoT +AkFVMQ8wDQYDVQQDEwZBbmRyZXkxHjAcBgkqhkiG9w0BCQEWD3Jvb3RAZ29vZ2xl +LmNvbTAeFw0xMjA5MDgxMTU2MzNaFw0xMzA5MDgxMTU2MzNaMGsxCzAJBgNVBAYT +AkJHMQ4wDAYDVQQIEwVTb2ZpYTEOMAwGA1UEBxMFU29maWExCzAJBgNVBAoTAkFV +MQ8wDQYDVQQDEwZBbmRyZXkxHjAcBgkqhkiG9w0BCQEWD3Jvb3RAZ29vZ2xlLmNv +bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA3NZOxBRLKUXK+j3T5rwHdNoQ +Q1BtjuXQ5KESvurYBAb0MbE8c+dExsCfEW0lfqlDp53GQcIZyOIu9UEhHszD+iOp +YkQPn1LXYd3GRb3Q6cYFOj9ecuScX55w3mSUeTuLXzMTffbd+BDhDK7mWdAjWCSY +G5iLXhNISyONfKFLO3MCAwEAAaOB0DCBzTAdBgNVHQ4EFgQUPp4dR3IT0t6bSE3Y +b91MyHJU2+8wgZ0GA1UdIwSBlTCBkoAUPp4dR3IT0t6bSE3Yb91MyHJU2++hb6Rt +MGsxCzAJBgNVBAYTAkJHMQ4wDAYDVQQIEwVTb2ZpYTEOMAwGA1UEBxMFU29maWEx +CzAJBgNVBAoTAkFVMQ8wDQYDVQQDEwZBbmRyZXkxHjAcBgkqhkiG9w0BCQEWD3Jv +b3RAZ29vZ2xlLmNvbYIJAIjfJkuxM1pAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN +AQEFBQADgYEAeBPoIRueOJ+SwpVniE2gmnvogWH9+irJnapDKGZrC/JsTA7ArqRd +EHO1xZxqF+v+v128LmwWAdaazgMUHjR7e7B0xo4Tcp+9+voczc/GBTO0wnp6HT76 +2kUB6rPwwg9bycW8hAJiJJtr3IW5eYMtXDqM4RrbxhA1n2EAaZPVa5s= +-----END CERTIFICATE----- diff --git a/src/examples/Makefile.am b/src/examples/Makefile.am @@ -9,20 +9,32 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/include \ @LIBGCRYPT_CFLAGS@ +AM_CFLAGS = -DDATADIR=\"$(top_srcdir)/src/datadir/\" + if USE_COVERAGE - AM_CFLAGS = --coverage + AM_CFLAGS += --coverage +endif + +if ENABLE_SPDY +spdyex = \ + spdy_event_loop \ + spdy_fileserver \ + spdy_response_with_callback endif + # example programs noinst_PROGRAMS = \ -minimal_example \ -dual_stack_example \ -minimal_example_comet \ -querystring_example \ -fileserver_example \ -fileserver_example_dirs \ -fileserver_example_external_select \ -refuse_post_example + minimal_example \ + dual_stack_example \ + minimal_example_comet \ + querystring_example \ + fileserver_example \ + fileserver_example_dirs \ + fileserver_example_external_select \ + refuse_post_example \ + $(spdyex) + if ENABLE_HTTPS noinst_PROGRAMS += https_fileserver_example @@ -116,3 +128,31 @@ https_fileserver_example_SOURCES = \ https_fileserver_example.c https_fileserver_example_LDADD = \ $(top_builddir)/src/microhttpd/libmicrohttpd.la + + +spdy_event_loop_SOURCES = \ + spdy_event_loop.c +spdy_event_loop_LDADD = \ + $(top_builddir)/src/microspdy/libmicrospdy.la \ + -lssl \ + -lcrypto \ + -lz \ + -ldl + +spdy_fileserver_SOURCES = \ + spdy_fileserver.c +spdy_fileserver_LDADD = \ + $(top_builddir)/src/microspdy/libmicrospdy.la \ + -lssl \ + -lcrypto \ + -lz \ + -ldl + +spdy_response_with_callback_SOURCES = \ + spdy_response_with_callback.c +spdy_response_with_callback_LDADD = \ + $(top_builddir)/src/microspdy/libmicrospdy.la \ + -lssl \ + -lcrypto \ + -lz \ + -ldl diff --git a/src/examples/spdy_event_loop.c b/src/examples/spdy_event_loop.c @@ -0,0 +1,444 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file event_loop.c + * @brief shows how to use the daemon. THIS IS MAINLY A TEST AND DEBUG + * PROGRAM + * @author Andrey Uzunov + */ + +#include "platform.h" +#include <unistd.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include "microspdy.h" +#include <sys/time.h> +#include <time.h> +#include <arpa/inet.h> +//#include "../framinglayer/structures.h" +//#include "../applicationlayer/alstructures.h" + +int run = 1; +int run2 = 1; + + + uint64_t loops; + time_t start; + + +void +new_session_callback (void *cls, + struct SPDY_Session * session) +{ + char ipstr[1024]; + + struct sockaddr *addr; + socklen_t addr_len = SPDY_get_remote_addr(session, &addr); + + if(!addr_len) + { + printf("SPDY_get_remote_addr"); + abort(); + } + + if(AF_INET == addr->sa_family) + { + struct sockaddr_in * addr4 = (struct sockaddr_in *) addr; + if(NULL == inet_ntop(AF_INET, &(addr4->sin_addr), ipstr, sizeof(ipstr))) + { + printf("inet_ntop"); + abort(); + } + printf("New connection from: %s:%i\n", ipstr, ntohs(addr4->sin_port)); + + } + else if(AF_INET6 == addr->sa_family) + { + struct sockaddr_in6 * addr6 = (struct sockaddr_in6 *) addr; + if(NULL == inet_ntop(AF_INET6, &(addr6->sin6_addr), ipstr, sizeof(ipstr))) + { + printf("inet_ntop"); + abort(); + } + printf("New connection from: %s:%i\n", ipstr, ntohs(addr6->sin6_port)); + + } +} + +void +session_closed_handler (void *cls, + struct SPDY_Session * session, + int by_client) +{ + //printf("session_closed_handler called\n"); + + if(SPDY_YES != by_client) + { + //killchild(child,"wrong by_client"); + printf("session closed by server\n"); + } + else + { + printf("session closed by client\n"); + } + + //session_closed_called = 1; +} + +void +response_done_callback(void *cls, + struct SPDY_Response *response, + struct SPDY_Request *request, + enum SPDY_RESPONSE_RESULT status, + bool streamopened) +{ + (void)streamopened; + if(strcmp(cls, "/close (daemon1)") == 0) + run = 0; + else { + if(strcmp(cls, "/close (daemon2)") == 0) run2 = 0; + loops = 0; + start = time(NULL); + } + if(SPDY_RESPONSE_RESULT_SUCCESS != status) + { + printf("not sent frame cause %i", status); + } + printf("answer for %s was sent\n", (char*)cls); + //printf("raw sent headers %s\n", (char *)(response->headers)+8); + + SPDY_destroy_request(request); + SPDY_destroy_response(response); + free(cls); +} + +int +print_headers (void *cls, + const char *name, const char *value) +{ + (void)cls; + printf("%s: %s\n",name,value); + return SPDY_YES; +} + +/* +void +new_request_cb (void *cls, + struct SPDY_Request * request, + uint8_t priority, + const char *method, + const char *path, + const char *version, + const char *host, + const char *scheme, + struct SPDY_NameValue * headers) +{ + (void)cls; + (void)request; + printf("Priority: %i\nHTTP headers, scheme: %s\n\n%s %s %s\nHost: %s\n", priority,scheme,method,path,version,host); + SPDY_name_value_iterate(headers, &print_headers, NULL); +} +*/ + +int +append_headers_to_data (void *cls, + const char *name, const char **value, int num_values) +{ + char **data = (char **)cls; + void *tofree = *data; + int i; + + if(num_values) + for(i=0;i<num_values;++i) + { + asprintf(data,"%s%s: %s\n", *data,name,value[i]); + } + else + asprintf(data,"%s%s: \n", *data,name); + + free(tofree); + return SPDY_YES; +} + +void +standard_request_handler(void *cls, + struct SPDY_Request * request, + uint8_t priority, + const char *method, + const char *path, + const char *version, + const char *host, + const char *scheme, + struct SPDY_NameValue * headers) +{ + char *html; + char *data; + struct SPDY_Response *response=NULL; + + printf("received request for '%s %s %s'\n", method, path, version); + if(strcmp(path,"/main.css")==0) + { + if(NULL != cls) + asprintf(&html,"body{color:green;}"); + else + asprintf(&html,"body{color:red;}"); + + //struct SPDY_NameValue *headers=SPDY_name_value_create(); + //SPDY_name_value_add(headers,"content-type","text/css"); + + response = SPDY_build_response(200,NULL,SPDY_HTTP_VERSION_1_1,NULL,html,strlen(html)); + free(html); + } + else + { + asprintf(&data,"Priority: %i\nHTTP headers, scheme: %s\n\n%s %s %s\nHost: %s\n", priority,scheme,method,path,version,host); + + SPDY_name_value_iterate(headers, &append_headers_to_data, &data); + + if(strcmp(path,"/close")==0) + { + asprintf(&html,"<html>" + "<body><b>Closing now!<br>This is an answer to the following " + "request:</b><br><br><pre>%s</pre></body></html>",data); + } + else + { + asprintf(&html,"<html><link href=\"main.css\" rel=\"stylesheet\" type=\"text/css\" />" + "<body><b>This is an answer to the following " + "request:</b><br><br><pre>%s</pre></body></html>",data); + } + + free(data); + + response = SPDY_build_response(200,NULL,SPDY_HTTP_VERSION_1_1,NULL,html,strlen(html)); + free(html); + } + + if(NULL==response){ + fprintf(stdout,"no response obj\n"); + abort(); + } + + char *pathcls; + asprintf(&pathcls, "%s (daemon%i)",path,NULL==cls ? 1 : 2); + if(SPDY_queue_response(request,response,true,false,&response_done_callback,pathcls)!=SPDY_YES) + { + fprintf(stdout,"queue\n"); + abort(); + } +} + +void sig_handler(int signo) +{ + printf("received signal\n"); +} + +int +main (int argc, char *const *argv) +{ + if(argc != 2) return 1; + + if (signal(SIGPIPE, sig_handler) == SIG_ERR) + printf("\ncan't catch SIGPIPE\n"); + + SPDY_init(); + + struct sockaddr_in addr4; + struct in_addr inaddr4; + inaddr4.s_addr = htonl(INADDR_ANY); + addr4.sin_family = AF_INET; + addr4.sin_addr = inaddr4; + addr4.sin_port = htons(atoi(argv[1])); + + struct SPDY_Daemon *daemon = SPDY_start_daemon(atoi(argv[1]), + DATADIR "cert-and-key.pem", + DATADIR "cert-and-key.pem", + &new_session_callback,&session_closed_handler,&standard_request_handler,NULL,NULL, + SPDY_DAEMON_OPTION_SESSION_TIMEOUT, 10, + //SPDY_DAEMON_OPTION_SOCK_ADDR, (struct sockaddr *)&addr4, + SPDY_DAEMON_OPTION_END); + + if(NULL==daemon){ + printf("no daemon\n"); + return 1; + } + + struct sockaddr_in6 addr6; + addr6.sin6_family = AF_INET6; + addr6.sin6_addr = in6addr_any; + addr6.sin6_port = htons(atoi(argv[1]) + 1); + + struct SPDY_Daemon *daemon2 = SPDY_start_daemon(atoi(argv[1]) + 1, + DATADIR "cert-and-key.pem", + DATADIR "cert-and-key.pem", + &new_session_callback,NULL,&standard_request_handler,NULL,&main, + //SPDY_DAEMON_OPTION_SESSION_TIMEOUT, 0, + //SPDY_DAEMON_OPTION_SOCK_ADDR, (struct sockaddr *)&addr6, + //SPDY_DAEMON_OPTION_FLAGS, SPDY_DAEMON_FLAG_ONLY_IPV6, + SPDY_DAEMON_OPTION_END); + + if(NULL==daemon2){ + printf("no daemon\n"); + return 1; + } + + do + { + unsigned long long timeoutlong=0; + struct timeval timeout; + volatile int rc; /* select() return code */ + volatile int ret; + + fd_set read_fd_set; + fd_set write_fd_set; + fd_set except_fd_set; + int maxfd = -1; + + if(run && daemon != NULL) + { + loops++; + FD_ZERO(&read_fd_set); + FD_ZERO(&write_fd_set); + FD_ZERO(&except_fd_set); + + ret = SPDY_get_timeout(daemon, &timeoutlong); + //printf("tout %i\n",timeoutlong); + if(SPDY_NO == ret || timeoutlong > 1) + { + //do sth else + //sleep(1); + + //try new connection + timeout.tv_sec = 1; + timeout.tv_usec = 0; + } + else + { + timeout.tv_sec = timeoutlong; + timeout.tv_usec = 0;//(timeoutlong % 1000) * 1000; + } + + printf("ret=%i; timeoutlong=%i; sec=%i; usec=%i\n", ret, timeoutlong, timeout.tv_sec, timeout.tv_usec); + //raise(SIGINT); + + /* get file descriptors from the transfers */ + maxfd = SPDY_get_fdset (daemon, + &read_fd_set, + &write_fd_set, + &except_fd_set); + +//struct timeval ts1,ts2; + //gettimeofday(&ts1, NULL); + rc = select(maxfd+1, &read_fd_set, &write_fd_set, &except_fd_set, &timeout); + //gettimeofday(&ts2, NULL); + printf("rc %i\n",rc); + // printf("time for select %i\n",ts2.tv_usec - ts1.tv_usec); + // printf("%i %i %i %i\n",ts1.tv_sec, ts1.tv_usec,ts2.tv_sec, ts2.tv_usec); + + switch(rc) { + case -1: + /* select error */ + break; + case 0: + + break; + default: + SPDY_run(daemon); + + break; + } + } + else if(daemon != NULL){ + + printf("%i loops in %i secs\n", loops, time(NULL) - start); + SPDY_stop_daemon(daemon); + daemon=NULL; + } + + if(run2) + { + FD_ZERO(&read_fd_set); + FD_ZERO(&write_fd_set); + FD_ZERO(&except_fd_set); + + ret = SPDY_get_timeout(daemon2, &timeoutlong); + //printf("tout %i\n",timeoutlong); + if(SPDY_NO == ret || timeoutlong > 1) + { + //do sth else + //sleep(1); + + //try new connection + timeout.tv_sec = 1; + timeout.tv_usec = 0; + } + else + { + timeout.tv_sec = timeoutlong; + timeout.tv_usec = 0;//(timeoutlong % 1000) * 1000; + } + + //printf("ret=%i; timeoutlong=%i; sec=%i; usec=%i\n", ret, timeoutlong, timeout.tv_sec, timeout.tv_usec); + //raise(SIGINT); + + /* get file descriptors from the transfers */ + maxfd = SPDY_get_fdset (daemon2, + &read_fd_set, + &write_fd_set, + &except_fd_set); + + rc = select(maxfd+1, &read_fd_set, &write_fd_set, &except_fd_set, &timeout); + + switch(rc) { + case -1: + /* select error */ + break; + case 0: + + break; + default: + SPDY_run(daemon2); + + break; + } + } + else if(daemon2 != NULL){ + SPDY_stop_daemon(daemon2); + daemon2=NULL; + } + } + while(run || run2); + + if(daemon != NULL){ + SPDY_stop_daemon(daemon); + } + if(daemon2 != NULL){ + SPDY_stop_daemon(daemon2); + } + + SPDY_deinit(); + + return 0; +} + diff --git a/src/examples/spdy_fileserver.c b/src/examples/spdy_fileserver.c @@ -0,0 +1,340 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2013 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file fileserver.c + * @brief Simple example how the lib can be used for serving + * files directly read from the system + * @author Andrey Uzunov + */ + +#include <unistd.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include "microspdy.h" +#include "time.h" + + +int run = 1; +char* basedir; + + +#define GET_MIME_TYPE(fname, mime) do {\ + uint __len = strlen(fname);\ + if (__len < 4 || '.' != (fname)[__len - 4]) break;\ + const char * __ext = &(fname)[__len - 3];\ + if(0 == strcmp(__ext, "jpg")) (mime) = strdup("image/jpeg");\ + else if(0 == strcmp(__ext, "png")) (mime) = strdup("image/png");\ + else if(0 == strcmp(__ext, "css")) (mime) = strdup("text/css");\ + else if(0 == strcmp(__ext, "gif")) (mime) = strdup("image/gif");\ + else if(0 == strcmp(__ext, "htm")) (mime) = strdup("text/html");\ + else \ + { \ + (mime) = strdup("application/octet-stream");\ + printf("MIME for %s is applic...\n", (fname));\ + }\ + if(NULL == (mime))\ + {\ + printf("no memory\n");\ + abort();\ + }\ + } while (0) + + +static const char *DAY_NAMES[] = + { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + +static const char *MONTH_NAMES[] = + { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + +//taken from http://stackoverflow.com/questions/2726975/how-can-i-generate-an-rfc1123-date-string-from-c-code-win32 +//and modified for linux +char *Rfc1123_DateTimeNow() +{ + const int RFC1123_TIME_LEN = 29; + time_t t; + struct tm tm; + char * buf = malloc(RFC1123_TIME_LEN+1); + + time(&t); + gmtime_r( &t, &tm); + + strftime(buf, RFC1123_TIME_LEN+1, "---, %d --- %Y %H:%M:%S GMT", &tm); + memcpy(buf, DAY_NAMES[tm.tm_wday], 3); + memcpy(buf+8, MONTH_NAMES[tm.tm_mon], 3); + + return buf; +} + + +ssize_t +response_callback (void *cls, + void *buffer, + size_t max, + bool *more) +{ + FILE *fd =(FILE*)cls; + + int ret = fread(buffer,1,max,fd); + *more = feof(fd) == 0; + + //if(!(*more)) + // fclose(fd); + + return ret; +} + + +void +response_done_callback(void *cls, + struct SPDY_Response *response, + struct SPDY_Request *request, + enum SPDY_RESPONSE_RESULT status, + bool streamopened) +{ + (void)streamopened; + //printf("answer for %s was sent\n", (char *)cls); + + /*if(SPDY_RESPONSE_RESULT_SUCCESS != status) + { + printf("answer for %s was NOT sent, %i\n", (char *)cls,status); + }*/ + + SPDY_destroy_request(request); + SPDY_destroy_response(response); + if(NULL!=cls)fclose(cls); +} + +void +standard_request_handler(void *cls, + struct SPDY_Request * request, + uint8_t priority, + const char *method, + const char *path, + const char *version, + const char *host, + const char *scheme, + struct SPDY_NameValue * headers) +{ + (void)cls; + (void)request; + (void)priority; + (void)host; + (void)scheme; + (void)headers; + + struct SPDY_Response *response=NULL; + struct SPDY_NameValue *resp_headers; + char *fname; + char *fsize; + char *mime=NULL; + char *date=NULL; + ssize_t filesize = -666; + FILE *fd = NULL; + int ret = -666; + + //printf("received request for '%s %s %s'\n", method, path, version); + if(strlen(path) > 1 && NULL == strstr(path, "..") && '/' == path[0]) + { + asprintf(&fname,"%s%s",basedir,path); + if(0 == access(fname, R_OK)) + { + if(NULL == (fd = fopen(fname,"r")) + || 0 != (ret = fseek(fd, 0L, SEEK_END)) + || -1 == (filesize = ftell(fd)) + || 0 != (ret = fseek(fd, 0L, SEEK_SET))) + { + printf("Error on opening %s\n%i %i %i\n",fname, fd, ret, filesize); + response = SPDY_build_response(SPDY_HTTP_INTERNAL_SERVER_ERROR,NULL,SPDY_HTTP_VERSION_1_1,NULL,NULL,0); + } + else + { + if(NULL == (resp_headers = SPDY_name_value_create())) + { + printf("SPDY_name_value_create failed\n"); + abort(); + } + + date = Rfc1123_DateTimeNow(); + if(NULL == date + || SPDY_YES != SPDY_name_value_add(resp_headers,SPDY_HTTP_HEADER_DATE,date)) + { + printf("SPDY_name_value_add or Rfc1123_DateTimeNow failed\n"); + abort(); + } + free(date); + + if(-1 == asprintf(&fsize, "%i", filesize) + || SPDY_YES != SPDY_name_value_add(resp_headers,SPDY_HTTP_HEADER_CONTENT_LENGTH,fsize)) + { + printf("SPDY_name_value_add or asprintf failed\n"); + abort(); + } + free(fsize); + + GET_MIME_TYPE(path,mime); + if(SPDY_YES != SPDY_name_value_add(resp_headers,SPDY_HTTP_HEADER_CONTENT_TYPE,mime)) + { + printf("SPDY_name_value_add failed\n"); + abort(); + } + free(mime); + + if(SPDY_YES != SPDY_name_value_add(resp_headers,SPDY_HTTP_HEADER_SERVER,"libmicrospdy/fileserver")) + { + printf("SPDY_name_value_add failed\n"); + abort(); + } + + response = SPDY_build_response_with_callback(200,NULL, + SPDY_HTTP_VERSION_1_1,resp_headers,&response_callback,fd,SPDY_MAX_SUPPORTED_FRAME_SIZE); + SPDY_name_value_destroy(resp_headers); + } + + if(NULL==response){ + printf("no response obj\n"); + abort(); + } + + if(SPDY_queue_response(request,response,true,false,&response_done_callback,fd)!=SPDY_YES) + { + printf("queue\n"); + abort(); + } + + free(fname); + return; + } + free(fname); + } + + if(strcmp(path,"/close")==0) + { + run = 0; + } + + response = SPDY_build_response(SPDY_HTTP_NOT_FOUND,NULL,SPDY_HTTP_VERSION_1_1,NULL,NULL,0); + printf("Not found %s\n",path); + + if(NULL==response){ + printf("no response obj\n"); + abort(); + } + + if(SPDY_queue_response(request,response,true,false,&response_done_callback,NULL)!=SPDY_YES) + { + printf("queue\n"); + abort(); + } +} + +int +main (int argc, char *const *argv) +{ + unsigned long long timeoutlong=0; + struct timeval timeout; + int ret; + fd_set read_fd_set; + fd_set write_fd_set; + fd_set except_fd_set; + int maxfd = -1; + struct SPDY_Daemon *daemon; + + if(argc != 5) + { + printf("Usage: %s cert-file key-file base-dir port\n", argv[0]); + return 1; + } + + SPDY_init(); + + daemon = SPDY_start_daemon(atoi(argv[4]), + argv[1], + argv[2], + NULL, + NULL, + &standard_request_handler, + NULL, + NULL, + SPDY_DAEMON_OPTION_SESSION_TIMEOUT, + 1800, + SPDY_DAEMON_OPTION_END); + + if(NULL==daemon){ + printf("no daemon\n"); + return 1; + } + + basedir = argv[3]; + timeout.tv_usec = 0; + + do + { + FD_ZERO(&read_fd_set); + FD_ZERO(&write_fd_set); + FD_ZERO(&except_fd_set); + + ret = SPDY_get_timeout(daemon, &timeoutlong); + if(SPDY_NO == ret || timeoutlong > 1) + { + //do sth else + //sleep(1); + + //try new connection + timeout.tv_sec = 1; + } + else + { + timeout.tv_sec = timeoutlong; + } + + maxfd = SPDY_get_fdset (daemon, + &read_fd_set, + &write_fd_set, + &except_fd_set); + + ret = select(maxfd+1, &read_fd_set, &write_fd_set, &except_fd_set, &timeout); + + switch(ret) { + case -1: + printf("select error: %i\n", errno); + break; + case 0: + + break; + default: + SPDY_run(daemon); + + break; + } + } + while(run); + + SPDY_stop_daemon(daemon); + + SPDY_deinit(); + + return 0; +} + diff --git a/src/examples/spdy_response_with_callback.c b/src/examples/spdy_response_with_callback.c @@ -0,0 +1,230 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2013 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file response_with_callback.c + * @brief shows how to create responses with callbacks + * @author Andrey Uzunov + */ + +#include <unistd.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include "microspdy.h" + +int run = 1; + + +ssize_t +response_callback (void *cls, + void *buffer, + size_t max, + bool *more) +{ + FILE *fd =(FILE*)cls; + + int ret = fread(buffer,1,max,fd); + *more = feof(fd) == 0; + + if(!(*more)) + fclose(fd); + + return ret; +} + + +void +response_done_callback(void *cls, + struct SPDY_Response *response, + struct SPDY_Request *request, + bool streamopened) +{ + (void)streamopened; + printf("answer for %s was sent\n", (char *)cls); + + SPDY_destroy_request(request); + SPDY_destroy_response(response); + free(cls); +} + +void +standard_request_handler(void *cls, + struct SPDY_Request * request, + uint8_t priority, + const char *method, + const char *path, + const char *version, + const char *host, + const char *scheme, + struct SPDY_NameValue * headers) +{ + (void)cls; + (void)request; + (void)priority; + (void)host; + (void)scheme; + (void)headers; + + char *html; + struct SPDY_Response *response=NULL; + struct SPDY_NameValue *resp_headers; + + printf("received request for '%s %s %s'\n", method, path, version); + if(strcmp(path,"/spdy-draft.txt")==0) + { + FILE *fd = fopen(DATADIR "spdy-draft.txt","r"); + + if(NULL == (resp_headers = SPDY_name_value_create())) + { + fprintf(stdout,"SPDY_name_value_create failed\n"); + abort(); + } + if(SPDY_YES != SPDY_name_value_add(resp_headers,SPDY_HTTP_HEADER_CONTENT_TYPE,"text/plain")) + { + fprintf(stdout,"SPDY_name_value_add failed\n"); + abort(); + } + + response = SPDY_build_response_with_callback(200,NULL, + SPDY_HTTP_VERSION_1_1,resp_headers,&response_callback,fd,SPDY_MAX_SUPPORTED_FRAME_SIZE); + SPDY_name_value_destroy(resp_headers); + } + else + { + if(strcmp(path,"/close")==0) + { + asprintf(&html,"<html>" + "<body><b>Closing now!</body></html>"); + run = 0; + } + else + { + asprintf(&html,"<html>" + "<body><a href=\"/spdy-draft.txt\">/spdy-draft.txt</a><br></body></html>"); + } + + response = SPDY_build_response(200,NULL,SPDY_HTTP_VERSION_1_1,NULL,html,strlen(html)); + free(html); + } + + if(NULL==response){ + fprintf(stdout,"no response obj\n"); + abort(); + } + + void *clspath = strdup(path); + + if(SPDY_queue_response(request,response,true,false,&response_done_callback,clspath)!=SPDY_YES) + { + fprintf(stdout,"queue\n"); + abort(); + } +} + +int +main (int argc, char *const *argv) +{ + unsigned long long timeoutlong=0; + struct timeval timeout; + int ret; + fd_set read_fd_set; + fd_set write_fd_set; + fd_set except_fd_set; + int maxfd = -1; + struct SPDY_Daemon *daemon; + + if(argc != 2) + { + return 1; + } + + SPDY_init(); + + daemon = SPDY_start_daemon(atoi(argv[1]), + DATADIR "cert-and-key.pem", + DATADIR "cert-and-key.pem", + NULL, + NULL, + &standard_request_handler, + NULL, + NULL, + SPDY_DAEMON_OPTION_SESSION_TIMEOUT, + 1800, + SPDY_DAEMON_OPTION_END); + + if(NULL==daemon){ + printf("no daemon\n"); + return 1; + } + + timeout.tv_usec = 0; + + do + { + FD_ZERO(&read_fd_set); + FD_ZERO(&write_fd_set); + FD_ZERO(&except_fd_set); + + ret = SPDY_get_timeout(daemon, &timeoutlong); + if(SPDY_NO == ret || timeoutlong > 1) + { + //do sth else + //sleep(1); + + //try new connection + timeout.tv_sec = 1; + } + else + { + timeout.tv_sec = timeoutlong; + } + + maxfd = SPDY_get_fdset (daemon, + &read_fd_set, + &write_fd_set, + &except_fd_set); + + ret = select(maxfd+1, &read_fd_set, &write_fd_set, &except_fd_set, &timeout); + + switch(ret) { + case -1: + printf("select error: %i\n", errno); + break; + case 0: + + break; + default: + SPDY_run(daemon); + + break; + } + } + while(run); + + SPDY_stop_daemon(daemon); + + SPDY_deinit(); + + return 0; +} + diff --git a/src/include/Makefile.am b/src/include/Makefile.am @@ -1,4 +1,9 @@ SUBDIRS = plibc . -include_HEADERS = microhttpd.h + +if ENABLE_SPDY +microspdy = microspdy.h +endif + +include_HEADERS = microhttpd.h $(microspdy) EXTRA_DIST = platform.h diff --git a/src/include/microspdy.h b/src/include/microspdy.h @@ -0,0 +1,1275 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012, 2013 Christian Grothoff + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file microspdy.h + * @brief public interface to libmicrospdy + * @author Andrey Uzunov + * @author Christian Grothoff + * + * All symbols defined in this header start with SPDY_. libmisrospdy is a small + * SPDY daemon library. The application can start multiple daemons + * and they are independent.<p> + * + * The header file defines various constants used by the SPDY and the HTTP protocol. + * This does not mean that the lib actually interprets all of these + * values. Not everything is implemented. The provided constants are exported as a convenience + * for users of the library. The lib does not verify that provided + * HTTP headers and if their values conform to the SPDY protocol, + * it only checks if the required headers for the SPDY requests and + * responses are provided.<p> + * + * The library uses just a single thread.<p> + * + * Before including "microspdy.h" you should add the necessary + * includes to define the types used in this file (which headers are needed may + * depend on your platform; for possible suggestions consult + * "platform.h" in the libmicrospdy distribution).<p> + * + * All of the functions returning SPDY_YES/SPDY_NO return + * SPDY_INPUT_ERROR when any of the parameters are invalid, e.g., + * required parameter is NULL. + */ +#ifndef SPDY_MICROSPDY_H +#define SPDY_MICROSPDY_H + +#include <zlib.h> +#include <stdbool.h> + +/** + * return code for "YES". + */ +#define SPDY_YES 1 + +/** + * return code for "NO". + */ +#define SPDY_NO 0 + +/** + * return code for error when input parameters are wrong. To be returned + * only by functions which return int. The others will return NULL on + * input error. + */ +#define SPDY_INPUT_ERROR -1 + +/** + * SPDY version supported by the lib. + */ +#define SPDY_VERSION 3 + +/** + * The maximum allowed size (without 8 byte headers) of + * SPDY frames (value length) is 8192. The lib will accept and + * send frames with length at most this value here. + */ +#define SPDY_MAX_SUPPORTED_FRAME_SIZE 8192 + +/** + * HTTP response codes. + */ +#define SPDY_HTTP_CONTINUE 100 +#define SPDY_HTTP_SWITCHING_PROTOCOLS 101 +#define SPDY_HTTP_PROCESSING 102 + +#define SPDY_HTTP_OK 200 +#define SPDY_HTTP_CREATED 201 +#define SPDY_HTTP_ACCEPTED 202 +#define SPDY_HTTP_NON_AUTHORITATIVE_INFORMATION 203 +#define SPDY_HTTP_NO_CONTENT 204 +#define SPDY_HTTP_RESET_CONTENT 205 +#define SPDY_HTTP_PARTIAL_CONTENT 206 +#define SPDY_HTTP_MULTI_STATUS 207 + +#define SPDY_HTTP_MULTIPLE_CHOICES 300 +#define SPDY_HTTP_MOVED_PERMANENTLY 301 +#define SPDY_HTTP_FOUND 302 +#define SPDY_HTTP_SEE_OTHER 303 +#define SPDY_HTTP_NOT_MODIFIED 304 +#define SPDY_HTTP_USE_PROXY 305 +#define SPDY_HTTP_SWITCH_PROXY 306 +#define SPDY_HTTP_TEMPORARY_REDIRECT 307 + +#define SPDY_HTTP_BAD_REQUEST 400 +#define SPDY_HTTP_UNAUTHORIZED 401 +#define SPDY_HTTP_PAYMENT_REQUIRED 402 +#define SPDY_HTTP_FORBIDDEN 403 +#define SPDY_HTTP_NOT_FOUND 404 +#define SPDY_HTTP_METHOD_NOT_ALLOWED 405 +#define SPDY_HTTP_METHOD_NOT_ACCEPTABLE 406 +#define SPDY_HTTP_PROXY_AUTHENTICATION_REQUIRED 407 +#define SPDY_HTTP_REQUEST_TIMEOUT 408 +#define SPDY_HTTP_CONFLICT 409 +#define SPDY_HTTP_GONE 410 +#define SPDY_HTTP_LENGTH_REQUIRED 411 +#define SPDY_HTTP_PRECONDITION_FAILED 412 +#define SPDY_HTTP_REQUEST_ENTITY_TOO_LARGE 413 +#define SPDY_HTTP_REQUEST_URI_TOO_LONG 414 +#define SPDY_HTTP_UNSUPPORTED_MEDIA_TYPE 415 +#define SPDY_HTTP_REQUESTED_RANGE_NOT_SATISFIABLE 416 +#define SPDY_HTTP_EXPECTATION_FAILED 417 +#define SPDY_HTTP_UNPROCESSABLE_ENTITY 422 +#define SPDY_HTTP_LOCKED 423 +#define SPDY_HTTP_FAILED_DEPENDENCY 424 +#define SPDY_HTTP_UNORDERED_COLLECTION 425 +#define SPDY_HTTP_UPGRADE_REQUIRED 426 +#define SPDY_HTTP_NO_RESPONSE 444 +#define SPDY_HTTP_RETRY_WITH 449 +#define SPDY_HTTP_BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS 450 +#define SPDY_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS 451 + +#define SPDY_HTTP_INTERNAL_SERVER_ERROR 500 +#define SPDY_HTTP_NOT_IMPLEMENTED 501 +#define SPDY_HTTP_BAD_GATEWAY 502 +#define SPDY_HTTP_SERVICE_UNAVAILABLE 503 +#define SPDY_HTTP_GATEWAY_TIMEOUT 504 +#define SPDY_HTTP_HTTP_VERSION_NOT_SUPPORTED 505 +#define SPDY_HTTP_VARIANT_ALSO_NEGOTIATES 506 +#define SPDY_HTTP_INSUFFICIENT_STORAGE 507 +#define SPDY_HTTP_BANDWIDTH_LIMIT_EXCEEDED 509 +#define SPDY_HTTP_NOT_EXTENDED 510 + +/** + * HTTP headers are used in SPDY, but all of them MUST be lowercase. + * Some are not valid in SPDY and MUST not be used + */ +#define SPDY_HTTP_HEADER_ACCEPT "accept" +#define SPDY_HTTP_HEADER_ACCEPT_CHARSET "accept-charset" +#define SPDY_HTTP_HEADER_ACCEPT_ENCODING "accept-encoding" +#define SPDY_HTTP_HEADER_ACCEPT_LANGUAGE "accept-language" +#define SPDY_HTTP_HEADER_ACCEPT_RANGES "accept-ranges" +#define SPDY_HTTP_HEADER_AGE "age" +#define SPDY_HTTP_HEADER_ALLOW "allow" +#define SPDY_HTTP_HEADER_AUTHORIZATION "authorization" +#define SPDY_HTTP_HEADER_CACHE_CONTROL "cache-control" +/* Connection header is forbidden in SPDY */ +#define SPDY_HTTP_HEADER_CONNECTION "connection" +#define SPDY_HTTP_HEADER_CONTENT_ENCODING "content-encoding" +#define SPDY_HTTP_HEADER_CONTENT_LANGUAGE "content-language" +#define SPDY_HTTP_HEADER_CONTENT_LENGTH "content-length" +#define SPDY_HTTP_HEADER_CONTENT_LOCATION "content-location" +#define SPDY_HTTP_HEADER_CONTENT_MD5 "content-md5" +#define SPDY_HTTP_HEADER_CONTENT_RANGE "content-range" +#define SPDY_HTTP_HEADER_CONTENT_TYPE "content-type" +#define SPDY_HTTP_HEADER_COOKIE "cookie" +#define SPDY_HTTP_HEADER_DATE "date" +#define SPDY_HTTP_HEADER_ETAG "etag" +#define SPDY_HTTP_HEADER_EXPECT "expect" +#define SPDY_HTTP_HEADER_EXPIRES "expires" +#define SPDY_HTTP_HEADER_FROM "from" +/* Host header is forbidden in SPDY */ +#define SPDY_HTTP_HEADER_HOST "host" +#define SPDY_HTTP_HEADER_IF_MATCH "if-match" +#define SPDY_HTTP_HEADER_IF_MODIFIED_SINCE "if-modified-since" +#define SPDY_HTTP_HEADER_IF_NONE_MATCH "if-none-match" +#define SPDY_HTTP_HEADER_IF_RANGE "if-range" +#define SPDY_HTTP_HEADER_IF_UNMODIFIED_SINCE "if-unmodified-since" +/* Keep-Alive header is forbidden in SPDY */ +#define SPDY_HTTP_HEADER_KEEP_ALIVE "keep-alive" +#define SPDY_HTTP_HEADER_LAST_MODIFIED "last-modified" +#define SPDY_HTTP_HEADER_LOCATION "location" +#define SPDY_HTTP_HEADER_MAX_FORWARDS "max-forwards" +#define SPDY_HTTP_HEADER_PRAGMA "pragma" +#define SPDY_HTTP_HEADER_PROXY_AUTHENTICATE "proxy-authenticate" +#define SPDY_HTTP_HEADER_PROXY_AUTHORIZATION "proxy-authorization" +/* Proxy-Connection header is forbidden in SPDY */ +#define SPDY_HTTP_HEADER_PROXY_CONNECTION "proxy-connection" +#define SPDY_HTTP_HEADER_RANGE "range" +#define SPDY_HTTP_HEADER_REFERER "referer" +#define SPDY_HTTP_HEADER_RETRY_AFTER "retry-after" +#define SPDY_HTTP_HEADER_SERVER "server" +#define SPDY_HTTP_HEADER_SET_COOKIE "set-cookie" +#define SPDY_HTTP_HEADER_SET_COOKIE2 "set-cookie2" +#define SPDY_HTTP_HEADER_TE "te" +#define SPDY_HTTP_HEADER_TRAILER "trailer" +/* Transfer-Encoding header is forbidden in SPDY */ +#define SPDY_HTTP_HEADER_TRANSFER_ENCODING "transfer-encoding" +#define SPDY_HTTP_HEADER_UPGRADE "upgrade" +#define SPDY_HTTP_HEADER_USER_AGENT "user-agent" +#define SPDY_HTTP_HEADER_VARY "vary" +#define SPDY_HTTP_HEADER_VIA "via" +#define SPDY_HTTP_HEADER_WARNING "warning" +#define SPDY_HTTP_HEADER_WWW_AUTHENTICATE "www-authenticate" + +/** + * HTTP versions (a value must be provided in SPDY requests/responses). + */ +#define SPDY_HTTP_VERSION_1_0 "HTTP/1.0" +#define SPDY_HTTP_VERSION_1_1 "HTTP/1.1" + +/** + * HTTP methods + */ +#define SPDY_HTTP_METHOD_CONNECT "CONNECT" +#define SPDY_HTTP_METHOD_DELETE "DELETE" +#define SPDY_HTTP_METHOD_GET "GET" +#define SPDY_HTTP_METHOD_HEAD "HEAD" +#define SPDY_HTTP_METHOD_OPTIONS "OPTIONS" +#define SPDY_HTTP_METHOD_POST "POST" +#define SPDY_HTTP_METHOD_PUT "PUT" +#define SPDY_HTTP_METHOD_TRACE "TRACE" + +/** + * HTTP POST encodings, see also + * http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4 + */ +#define SPDY_HTTP_POST_ENCODING_FORM_URLENCODED "application/x-www-form-urlencoded" +#define SPDY_HTTP_POST_ENCODING_MULTIPART_FORMDATA "multipart/form-data" + + +/** + * Handle for the daemon (listening on a socket). + */ +struct SPDY_Daemon; + + +/** + * Handle for a SPDY session/connection. + */ +struct SPDY_Session; + + +/** + * Handle for a SPDY request sent by a client. The structure has pointer + * to the session's handler + */ +struct SPDY_Request; + + +/** + * Handle for a response containing HTTP headers and data to be sent. + * The structure has pointer to the session's handler + * for this response. + */ +struct SPDY_Response; + + +/** + * Collection of tuples of an HTTP header and values used in requests + * and responses. + */ +struct SPDY_NameValue; + + +/** + * Collection of tuples of a SPDY setting ID, value + * and flags used to control the sessions. + */ +struct SPDY_Settings; + + +/** + * SPDY daemon options. Passed in the varargs portion of + * SPDY_start_daemon to customize the daemon. Each option must + * be followed by a value of a specific type.<p> + * + * The values are used internally as flags, that is why they must be + * powers of 2. + */ +enum SPDY_DAEMON_OPTION +{ + + /** + * No more options / last option. This is used + * to terminate the VARARGs list. + */ + SPDY_DAEMON_OPTION_END = 0, + + /** + * Set a custom timeout for all connections. Must be followed by + * a number of seconds, given as an 'unsigned int'. Use + * zero for no timeout. + */ + SPDY_DAEMON_OPTION_SESSION_TIMEOUT = 1, + + /** + * Bind daemon to the supplied sockaddr. This option must be + * followed by a 'struct sockaddr *'. The 'struct sockaddr*' + * should point to a 'struct sockaddr_in6' or to a + * 'struct sockaddr_in'. + */ + SPDY_DAEMON_OPTION_SOCK_ADDR = 2, + + /** + * Flags for the daemon. Must be followed by a SPDY_DAEMON_FLAG value + * which is the result of bitwise OR of desired flags. + */ + SPDY_DAEMON_OPTION_FLAGS = 4, +}; + + +/** + * Flags for starting SPDY daemon. They are used to set some settings + * for the daemon, which do not require values. + */ +enum SPDY_DAEMON_FLAG +{ + /** + * No flags selected. + */ + SPDY_DAEMON_FLAG_NO = 0, + + /** + * The server will bind only on IPv6 addresses. If the flag is set and + * the daemon is provided with IPv4 address or IPv6 is not supported, + * starting daemon will fail. + */ + SPDY_DAEMON_FLAG_ONLY_IPV6 = 1, +}; + + +/** + * SPDY settings IDs sent by both client and server in SPDY SETTINGS frame. + * They affect the whole SPDY session. Defined in SPDY Protocol - Draft 3. + */ +enum SPDY_SETTINGS +{ + + /** + * Allows the sender to send its expected upload bandwidth on this + * channel. This number is an estimate. The value should be the + * integral number of kilobytes per second that the sender predicts + * as an expected maximum upload channel capacity. + */ + SPDY_SETTINGS_UPLOAD_BANDWIDTH = 1, + + /** + * Allows the sender to send its expected download bandwidth on this + * channel. This number is an estimate. The value should be the + * integral number of kilobytes per second that the sender predicts as + * an expected maximum download channel capacity. + */ + SPDY_SETTINGS_DOWNLOAD_BANDWIDTH = 2, + + /** + * Allows the sender to send its expected round-trip-time on this + * channel. The round trip time is defined as the minimum amount of + * time to send a control frame from this client to the remote and + * receive a response. The value is represented in milliseconds. + */ + SPDY_SETTINGS_ROUND_TRIP_TIME = 3, + + /** + * Allows the sender to inform the remote endpoint the maximum number + * of concurrent streams which it will allow. By default there is no + * limit. For implementors it is recommended that this value be no + * smaller than 100. + */ + SPDY_SETTINGS_MAX_CONCURRENT_STREAMS = 4, + + /** + * Allows the sender to inform the remote endpoint of the current TCP + * CWND value. + */ + SPDY_SETTINGS_CURRENT_CWND = 5, + + /** + * Allows the sender to inform the remote endpoint the retransmission + * rate (bytes retransmitted / total bytes transmitted). + */ + SPDY_SETTINGS_DOWNLOAD_RETRANS_RATE = 6, + + /** + * Allows the sender to inform the remote endpoint the initial window + * size (in bytes) for new streams. + */ + SPDY_SETTINGS_INITIAL_WINDOW_SIZE = 7, + + /** + * Allows the server to inform the client if the new size of the + * client certificate vector. + */ + SPDY_SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE = 8, +}; + + +/** + * Flags for each individual SPDY setting in the SPDY SETTINGS frame. + * They affect only one setting to which they are set. + * Defined in SPDY Protocol - Draft 3. + */ +enum SPDY_FLAG_SETTINGS{ + + /** + * When set, the sender of this SETTINGS frame is requesting that the + * recipient persist the ID/Value and return it in future SETTINGS + * frames sent from the sender to this recipient. Because persistence + * is only implemented on the client, this flag is only sent by the + * server. + */ + SPDY_FLAG_SETTINGS_PERSIST_VALUE = 1, + + /** + * When set, the sender is notifying the recipient that this ID/Value + * pair was previously sent to the sender by the recipient with the + * SPDY_FLAG_SETTINGS_PERSIST_VALUE, and the sender is returning it. + * Because persistence is only implemented on the client, this flag is + * only sent by the client. + */ + SPDY_FLAG_SETTINGS_PERSISTED = 2, +}; + + +/** + * Flag associated with a whole SPDY SETTINGS frame. Affect all the + * settings in the frame. Defined in SPDY Protocol - Draft 3. + */ +enum SPDY_FLAG_SETTINGS_FRAME +{ + + /** + * When set, the client should clear any previously persisted SETTINGS + * ID/Value pairs. If this frame contains ID/Value pairs with the + * SPDY_FLAG_SETTINGS_PERSIST_VALUE set, then the client will first + * clear its existing, persisted settings, and then persist the values + * with the flag set which are contained within this frame. Because + * persistence is only implemented on the client, this flag can only + * be used when the sender is the server. + */ + SPDY_FLAG_SETTINGS_CLEAR_SETTINGS = 1, +}; + + +/** + * SPDY settings function options. Passed in the varargs portion of + * SPDY_SettingsReceivedCallback and SPDY_send_settings to customize + * more the settings handling. Each option must + * be followed by a value of a specific type.<p> + * + * The values are used internally as flags, that is why they must be + * powers of 2. + */ +enum SPDY_SETTINGS_OPTION +{ + + /** + * No more options / last option. This is used + * to terminate the VARARGs list. + */ + SPDY_SETTINGS_OPTION_END = 0, +}; + + +/** + * Used as a parameter for SPDY_ResponseResultCallback and shows if the + * response was actually written to the TLS socket or discarded by the + * lib for any reason (and respectively the reason). + */ +enum SPDY_RESPONSE_RESULT +{ + + /** + * The lib has written the full response to the TLS socket. + */ + SPDY_RESPONSE_RESULT_SUCCESS = 0, + + /** + * The session is being closed, so the data is being discarded + */ + SPDY_RESPONSE_RESULT_SESSION_CLOSED = 1, + + /** + * The stream for this response has been closed. May happen when the + * sender had sent first SYN_STREAM and after that RST_STREAM. + */ + SPDY_RESPONSE_RESULT_STREAM_CLOSED = 2, +}; + + +/** + * Callback for serious error condition. The default action is to print + * an error message and abort(). + * + * @param cls user specified value + * @param file where the error occured + * @param line where the error occured + * @param reason error details message, may be NULL + */ +typedef void +(*SPDY_PanicCallback) (void * cls, + const char * file, + unsigned int line, + const char * reason); + + +/** + * Callback for new SPDY session established by a client. Called + * immediately after the TCP connection was established. + * + * @param cls client-defined closure + * @param session handler for the new SPDY session + */ +typedef void +(*SPDY_NewSessionCallback) (void * cls, + struct SPDY_Session * session); + + +/** + * Callback for closed session. Called after the TCP connection was + * closed. In this callback function the user has the last + * chance to access the SPDY_Session structure. After that the latter + * will be cleaned! + * + * @param cls client-defined closure + * @param session handler for the closed SPDY session + * @param by_client SPDY_YES if the session close was initiated by the + * client; + * SPDY_NO if closed by the server + */ +typedef void +(*SPDY_SessionClosedCallback) (void * cls, + struct SPDY_Session * session, + int by_client); + + +/** + * Iterator over name-value pairs. + * + * @param cls client-defined closure + * @param name of the pair + * @param value of the pair + * @return SPDY_YES to continue iterating, + * SPDY_NO to abort the iteration + */ +typedef int +(*SPDY_NameValueIterator) (void * cls, + const char * name, + const char * const * value, + int num_values); + + +/** + * Callback for received SPDY request. + * + * @param cls client-defined closure + * @param request handler. The request object is required for + * sending responses. + * @param priority of the SPDY stream which the request was + * sent over + * @param method HTTP method + * @param path HTTP path + * @param version HTTP version just like in HTTP request/response: + * "HTTP/1.0" or "HTTP/1.1" currently + * @param host called host as in HTTP + * @param scheme used ("http" or "https"). In SPDY 3 it is only "https". + * @param headers other HTTP headers from the request + */ +typedef void (*SPDY_NewRequestCallback) (void * cls, + struct SPDY_Request * request, + uint8_t priority, + const char * method, + const char * path, + const char * version, + const char * host, + const char * scheme, + struct SPDY_NameValue * headers); + + +/** + * Callback for received new data chunk from the POST data of a given + * request. + * + * @param cls client-defined closure + * @param request handler + * @param buf data chunk from the POST data + * @param size the size of the data chunk 'buf' in bytes + * @param more false if this is the last chunk from the POST data. Note: + * true does not mean that more data will come, exceptional + * situation is possible + * @return SPDY_YES to continue calling the function, + * SPDY_NO to stop calling the function for this request + */ +typedef int (*SPDY_NewPOSTDataCallback) (void * cls, + struct SPDY_Request *request, + const void * buf, + size_t size, + bool more); +// How about passing POST encoding information +// here as well? +//TODO + + +/** + * Callback to be used with SPDY_build_response_with_callback. The + * callback will be called when the lib wants to write to the TLS socket. + * The application should provide the data to be sent. + * + * @param cls client-defined closure + * @param max maximum number of bytes that are allowed to be written + * to the buffer. + * @param more true if more data will be sent (i.e. the function must + * be calleed again), + * false if this is the last chunk, the lib will close + * the stream + * @return number of bytes written to buffer. On error the call MUST + * return value less than 0 to indicate the library. + */ +typedef ssize_t (*SPDY_ResponseCallback) (void * cls, + void * buffer, + size_t max, + bool * more); + + +/** + * Callback to be called when the last bytes from the response was sent + * to the client or when the response was discarded from the lib. This + * callback is a very good place to discard the request and the response + * objects, if they will not be reused (e.g., sending the same response + * again). If the stream is closed it is safe to discard the request + * object. + * + * @param cls client-defined closure + * @param response handler to the response that was just sent + * @param request handler to the request for which the response was sent + * @param status shows if actually the response was sent or it was + * discarded by the lib for any reason (e.g., closing session, + * closing stream, stopping daemon, etc.). It is possible that + * status indicates an error but parts of the response headers + * and/or body (in one + * or several frames) were already sent to the client. + * @param streamopened indicates if the the stream for this request/ + * response pair is still opened. If yes, the server may want + * to use SPDY push to send something additional to the client + * and/or close the stream. + */ +typedef void +(*SPDY_ResponseResultCallback) (void * cls, + struct SPDY_Response * response, + struct SPDY_Request * request, + enum SPDY_RESPONSE_RESULT status, + bool streamopened); + + +/** + * Callback to notify when SPDY ping response is received. + * + * @param session handler for which the ping request was sent + * @param rtt the timespan between sending ping request and receiving it + * from the library + */ +typedef void +(*SPDY_PingCallback) (void * cls, + struct SPDY_Session * session, + struct timeval * rtt); + + +/** + * Iterator over settings ID/Value/Flags tuples. + * + * @param cls client-defined closure + * @param id SPDY settings ID + * @param value value for this setting + * @param flags flags for this tuple; use + * enum SPDY_FLAG_SETTINGS + * @return SPDY_YES to continue iterating, + * SPDY_NO to abort the iteration + */ +typedef int +(*SPDY_SettingsIterator) (void * cls, + enum SPDY_SETTINGS id, + int32_t value, + uint8_t flags); + + +/** + * Callback to notify when SPDY SETTINGS are received from the client. + * + * @param session handler for which settings are received + * @param settings ID/value/flags tuples of the settings + * @param flags for the whole settings frame; use + * enum SPDY_FLAG_SETTINGS_FRAME + * @param ... list of options (type-value pairs, + * terminated with SPDY_SETTINGS_OPTION_END). + */ +typedef void +(*SPDY_SettingsReceivedCallback) (struct SPDY_Session * session, + struct SPDY_Settings * settings, + uint8_t flags, + ...); + + +/* Global functions for the library */ + + +/** + * Init function for the whole library. It MUST be called before any + * other function of the library to initialize things like TLS context + * and possibly other stuff needed by the lib. Currently the call + * always returns SPDY_YES. + * + * @return SPDY_YES if the library was correctly initialized and its + * functions can be used now; + * SPDY_NO on error + */ +int +SPDY_init (void); + + +/** + * Deinit function for the whole lib. It can be called after finishing + * using the library. It frees and cleans up resources allocated in + * SPDY_init. Currently the function does not do anything. + */ +void +SPDY_deinit (void); + + +/** + * Sets the global error handler to a different implementation. "cb" + * will only be called in the case of typically fatal, serious + * internal consistency issues. These issues should only arise in the + * case of serious memory corruption or similar problems with the + * architecture as well as failed assertions. While "cb" is allowed to + * return and the lib will then try to continue, this is never safe. + * + * The default implementation that is used if no panic function is set + * simply prints an error message and calls "abort". Alternative + * implementations might call "exit" or other similar functions. + * + * @param cb new error handler + * @param cls passed to error handler + */ +void +SPDY_set_panic_func (SPDY_PanicCallback cb, + void *cls); + + +/* Daemon functions */ + + +/** + * Start a SPDY webserver on the given port. + * + * @param port to bind to. The value is ignored if address structure + * is passed as daemon option + * @param certfile path to the certificate that will be used by server + * @param keyfile path to the keyfile for the certificate + * @param nscb callback called when a new SPDY session is + * established by a client + * @param sccb callback called when a session is closed + * @param nrcb callback called when a client sends request + * @param npdcb callback called when HTTP POST params are received + * after request + * @param cls common extra argument to all of the callbacks + * @param ... list of options (type-value pairs, + * terminated with SPDY_DAEMON_OPTION_END). + * @return NULL on error, handle to daemon on success + */ +struct SPDY_Daemon * +SPDY_start_daemon (uint16_t port, + const char * certfile, + const char * keyfile, + SPDY_NewSessionCallback nscb, + SPDY_SessionClosedCallback sccb, + SPDY_NewRequestCallback nrcb, + SPDY_NewPOSTDataCallback npdcb, + void * cls, + ...); + + +/** + * Shutdown the daemon. First all sessions are closed. It is NOT safe + * to call this function in user callbacks. + * + * @param daemon to stop + */ +void +SPDY_stop_daemon (struct SPDY_Daemon *daemon); + + +/** + * Obtain the select sets for this daemon. Only those are retrieved, + * which some processing should be done for, i.e. not all sockets are + * added to write_fd_set.<p> + * + * It is possible that there is + * nothing to be read from a socket but there is data either in the + * TLS subsystem's read buffers or in libmicrospdy's read buffers, which + * waits for being processed. In such case the file descriptor will be + * added to write_fd_set. Since it is very likely for the socket to be + * ready for writing, the select used in the application's event loop + * will return with success, SPDY_run will be called, the data will be + * processed and maybe something will be written to the socket. Without + * this behaviour, considering a proper event loop, data may stay in the + * buffers, but run is never called. + * + * @param daemon to get sets from + * @param read_fd_set read set + * @param write_fd_set write set + * @param except_fd_set except set + * @return largest FD added to any of the sets + */ +int +SPDY_get_fdset (struct SPDY_Daemon * daemon, + fd_set * read_fd_set, + fd_set * write_fd_set, + fd_set * except_fd_set); + + +/** + * Obtain timeout value for select for this daemon. The returned value + * is how long select + * should at most block, not the timeout value set for connections. + * + * @param daemon to query for timeout + * @param timeout will be set to the timeout value (in seconds) + * @return SPDY_YES on success + * SPDY_NO if no connections exist that + * would necessiate the use of a timeout right now + */ +int +SPDY_get_timeout (struct SPDY_Daemon * daemon, + unsigned long long * timeout); + + +/** + * Run webserver operations. This method must be called in + * the client event loop. + * + * @param daemon to run + */ +void +SPDY_run (struct SPDY_Daemon *daemon); + + +/* SPDY Session handling functions */ + + +/** + * Closes a SPDY session. SPDY clients and servers are expected to keep + * sessions opened as long as possible. However, the server may want to + * close some connections, e.g. if there are too many, to free some + * resources. The function can also be used to close a specific session + * if the client is not desired. + * + * @param session handler to be closed + */ +void +SPDY_close_session(struct SPDY_Session * session); + + +/** + * Associate a void pointer with a session. The data accessible by the + * pointer can later be used wherever the session handler is available. + * + * @param session handler + * @param cls any data pointed by a pointer to be accessible later + */ +void +SPDY_set_cls_to_session(struct SPDY_Session * session, + void * cls); + + +/** + * Retrieves the pointer associated with SPDY_set_cls_to_session(). + * + * @param session handler to get its cls + * @return same pointer added by SPDY_set_cls_to_session() or + * NULL when nothing was associated + */ +void * +SPDY_get_cls_from_session(struct SPDY_Session * session); + + +/** + * Retrieves the remote address of a given session. + * + * @param session handler to get its remote address + * @param addr out parameter; pointing to remote address + * @return length of the address structure + */ +socklen_t +SPDY_get_remote_addr(struct SPDY_Session * session, + struct sockaddr ** addr); + + +/* SPDY name/value data structure handling functions */ + + +/** + * Create a new NameValue structure. It is needed for putting inside the + * HTTP headers and their values for a response. The user should later + * destroy alone the structure. + * + * @return hendler to the new empty structure or NULL on error + */ +struct SPDY_NameValue * +SPDY_name_value_create (); + + +/** + * Add name/value pair to a NameValue structure. SPDY_NO will be returned + * if the name/value pair is already in the structure. It is legal to + * add different values for the same name. + * + * @param container structure to which the new pair is added + * @param name for the value. Null-terminated string. + * @param value the value itself. Null-terminated string. + * @return SPDY_NO on error or SPDY_YES on success + */ +int +SPDY_name_value_add (struct SPDY_NameValue * container, + const char * name, + const char * value); + + +/** + * Lookup value for a name in a name/value structure. + * + * @param container structure in which to lookup + * @param name the name to look for + * @param num_values length of the returned array with values + * @return NULL if no such item was found, or an array containing the + * values + */ +const char * const * +SPDY_name_value_lookup (struct SPDY_NameValue *container, + const char *name, + int * num_values); + + +/** + * Iterate over name/value structure. + * + * @param container structure which to iterate over + * @param iterator callback to call on each name/value pair; + * maybe NULL (then just count headers) + * @param iterator_cls extra argument to iterator + * @return number of entries iterated over + */ +int +SPDY_name_value_iterate (struct SPDY_NameValue *container, + SPDY_NameValueIterator iterator, + void *iterator_cls); + + +/** + * Destroy a NameValue structure. Use this function to destroy only + * objects which, after passed to, will not be destroied by other + * functions. + * + */ +void +SPDY_name_value_destroy (struct SPDY_NameValue * container); + + +/* SPDY request handling functions */ + + +/** + * Gets the session responsible for the given + * request. + * + * @param request for which the session is wanted + * @return session handler for the request + */ +struct SPDY_Session * +SPDY_get_session_for_request(const struct SPDY_Request * request); + + +/** + * Associate a void pointer with a request. The data accessible by the + * pointer can later be used wherever the request handler is available. + * + * @param request with which to associate a pointer + * @param cls any data pointed by a pointer to be accessible later + */ +void +SPDY_set_cls_to_request(struct SPDY_Request * request, + void * cls); + + +/** + * Retrieves the pointer associated with the request by + * SPDY_set_cls_to_request(). + * + * @param request to get its cls + * @return same pointer added by SPDY_set_cls_to_request() or + * NULL when nothing was associated + */ +void * +SPDY_get_cls_from_request(struct SPDY_Request * request); + + +/* SPDY response handling functions */ + + +/** + * Create response object containing all needed headers and data. The + * response object is not bound to a request, so it can be used multiple + * times with SPDY_queue_response() and schould be + * destroied by calling the SPDY_destroy_response().<p> + * + * Currently the library does not provide compression of the body data. + * It is up to the user to pass already compressed data and the + * appropriate headers to this function when desired. + * + * @param status HTTP status code for the response (e.g. 404) + * @param statustext HTTP status message for the response, which will + * be appended to the status code (e.g. "OK"). Can be NULL + * @param version HTTP version for the response (e.g. "http/1.1") + * @param headers name/value structure containing additional HTTP headers. + * Can be NULL. Can be used multiple times, it is up to + * the user to destoy the object when not needed anymore. + * @param data the body of the response. The lib will make a copy of it, + * so it is up to the user to take care of the memory + * pointed by data + * @param size length of data. It can be 0, then the lib will send only + * headers + * @return NULL on error, handle to response object on success + */ +struct SPDY_Response * +SPDY_build_response(int status, + const char * statustext, + const char * version, + struct SPDY_NameValue * headers, + const void * data, + size_t size); + + +/** + * Create response object containing all needed headers. The data will + * be provided later when the lib calls the callback function (just + * before writing it to the TLS socket). The + * response object is not bound to a request, so it can be used multiple + * times with SPDY_queue_response() and schould be + * destroied by calling the SPDY_destroy_response().<p> + * + * Currently the library does not provide compression of the body data. + * It is up to the user to pass already compressed data and the + * appropriate headers to this function and the callback when desired. + * + * @param status HTTP status code for the response (e.g. 404) + * @param statustext HTTP status message for the response, which will + * be appended to the status code (e.g. "OK"). Can be NULL + * @param version HTTP version for the response (e.g. "http/1.1") + * @param headers name/value structure containing additional HTTP headers. + * Can be NULL. Can be used multiple times, it is up to + * the user to destoy the object when not needed anymore. + * @param rcb callback to use to obtain response data + * @param rcb_cls extra argument to rcb + * @param block_size preferred block size for querying rcb (advisory only, + * the lib will call rcb specifying the block size); clients + * should pick a value that is appropriate for IO and + * memory performance requirements. The function will + * fail if the value is bigger than the maximum + * supported value (SPDY_MAX_SUPPORTED_FRAME_SIZE). + * Can be 0, then the lib will use + * SPDY_MAX_SUPPORTED_FRAME_SIZE instead. + * @return NULL on error, handle to response object on success + */ +struct SPDY_Response * +SPDY_build_response_with_callback(int status, + const char * statustext, + const char * version, + struct SPDY_NameValue * headers, + SPDY_ResponseCallback rcb, + void *rcb_cls, + uint32_t block_size); + + +/** + * Queue response object to be sent to the client. A successfully queued + * response may never be sent, e.g. when the stream gets closed. The + * data will be added to the output queue. The call will fail, if the + * output for this session + * is closed (i.e. the session is closed, half or full) or the output + * channel for the stream, on which the request was received, is closed + * (i.e. the stream is closed, half or full). + * + * @param request object identifying the request to which the + * response is returned + * @param response object containg headers and data to be sent + * @param closestream TRUE if the server does NOT intend to PUSH + * something more associated to this request/response later, + * FALSE otherwise + * @param consider_priority if FALSE, the response will be added to the + * end of the queue. If TRUE, the response will be added after + * the last previously added response with priority of the + * request grater or equal to that of the current one. This + * means that the function should be called with TRUE each time + * if one wants to be sure that the output queue behaves like + * a priority queue + * @param rrcb callback called when all the data was sent (last frame + * from response) or when that frame was discarded (e.g. the + * stream has been closed meanwhile) + * @param rrcb_cls extra argument to rcb + * @return SPDY_NO on error or SPDY_YES on success + */ +int +SPDY_queue_response (struct SPDY_Request * request, + struct SPDY_Response *response, + bool closestream, + bool consider_priority, + SPDY_ResponseResultCallback rrcb, + void * rrcb_cls); + + +/** + * Destroy a response structure. It should be called for all objects + * returned by SPDY_build_response*() functions to free the memory + * associated with the prepared response. It is safe to call this + * function not before being sure that the response will not be used by + * the lib anymore, this means after SPDY_ResponseResultCallback + * callbacks were called for all calls to SPDY_queue_response() passing + * this response. + * + * @param response to destroy + */ +void +SPDY_destroy_response (struct SPDY_Response *response); + + +/* SPDY settings ID/value data structure handling functions */ + + +/** + * Create a new SettingsIDValue structure. It is needed for putting + * inside tuples of SPDY option, flags and value for sending to the + * client. + * + * @return hendler to the new empty structure or NULL on error + */ +const struct SPDY_Settings * +SPDY_settings_create (); + + +/** + * Add or update a tuple to a SettingsIDValue structure. + * + * @param container structure to which the new tuple is added + * @param id SPDY settings ID that will be sent. If this ID already in + * container, the tupple for it will be updated (value and/or + * flags). If it is not in the container, a new tupple will be + * added. + * @param flags SPDY settings flags applied only to this setting + * @param value of the setting + * @return SPDY_NO on error + * or SPDY_YES if a new setting was added + */ +int +SPDY_settings_add (struct SPDY_Settings *container, + enum SPDY_SETTINGS id, + enum SPDY_FLAG_SETTINGS flags, + int32_t value); + + +/** + * Lookup value and flags for an ID in a settings ID/value structure. + * + * @param container structure in which to lookup + * @param id SPDY settings ID to search for + * @param flags out param for SPDY settings flags for this setting; + * check it against the flags in enum SPDY_FLAG_SETTINGS + * @param value out param for the value of this setting + * @return SPDY_NO if the setting is not into the structure + * or SPDY_YES if it is into it + */ +int +SPDY_settings_lookup (const struct SPDY_Settings * container, + enum SPDY_SETTINGS id, + enum SPDY_FLAG_SETTINGS * flags, + int32_t * value); + + +/** + * Iterate over settings ID/value structure. + * + * @param container structure which to iterate over + * @param iterator callback to call on each ID/value pair; + * maybe NULL (then just count number of settings) + * @param iterator_cls extra argument to iterator + * @return number of entries iterated over + */ +int +SPDY_settings_iterate (const struct SPDY_Settings * container, + SPDY_SettingsIterator iterator, + void * iterator_cls); + + +/** + * Destroy a settings ID/value structure. Use this function to destroy + * only objects which, after passed to, will not be destroied by other + * functions. + * + * @param container structure which to detroy + */ +void +SPDY_settings_destroy (struct SPDY_Settings * container); + + +/* SPDY SETTINGS handling functions */ + + +/** + * Send SPDY SETTINGS to the client. The call will return fail if there + * in invald setting into the settings container (e.g. invalid setting + * ID). + * + * @param session SPDY_Session handler for which settings are being sent + * @param settings ID/value pairs of the settings to be sent. + * Can be used multiple times, it is up to the user to destoy + * the object when not needed anymore. + * @param flags for the whole settings frame. They are valid for all tuples + * @param ... list of options (type-value pairs, + * terminated with SPDY_SETTINGS_OPTION_END). + * @return SPDY_NO on error or SPDY_YES on + * success + */ +int +SPDY_send_settings (struct SPDY_Session * session, + struct SPDY_Settings * settings, + enum SPDY_FLAG_SETTINGS_FRAME flags, + ...); + + +/* SPDY misc functions */ + + +/** + * Destroy a request structure. It should be called for all objects + * received as a parameter in SPDY_NewRequestCallback to free the memory + * associated with the request. It is safe to call this + * function not before being sure that the request will not be used by + * the lib anymore, this means after the stream, on which this request + * had been sent, was closed and all SPDY_ResponseResultCallback + * callbacks were called for all calls to SPDY_queue_response() passing + * this request object. + * + * @param request to destroy + */ +void +SPDY_destroy_request (struct SPDY_Request * request); + + +/** + * Send SPDY ping to the client + * + * @param session handler for which the ping request is sent + * @param rttcb callback called when ping response to the request is + * received + * @param rttcb_cls extra argument to rttcb + * @return SPDY_NO on error or SPDY_YES on success + */ +int +SPDY_send_ping(struct SPDY_Session * session, + SPDY_PingCallback rttcb, + void * rttcb_cls); + + +#endif diff --git a/src/microspdy/EXPORT.sym b/src/microspdy/EXPORT.sym @@ -0,0 +1,2 @@ +SPDY_start_daemon +SPDY_stop_daemon diff --git a/src/microspdy/Makefile.am b/src/microspdy/Makefile.am @@ -0,0 +1,38 @@ +if USE_PRIVATE_PLIBC_H + PLIBC_INCLUDE = -I$(top_srcdir)/src/include/plibc +endif + +AM_CPPFLAGS = \ + $(PLIBC_INCLUDE) \ + -I$(top_srcdir)/src/include \ + -I$(top_srcdir)/src/microspdy + + +EXTRA_DIST = EXPORT.sym + + +lib_LTLIBRARIES = \ + libmicrospdy.la + +libmicrospdy_la_SOURCES = \ + tls.h tls.c \ + structures.h structures.c \ + internal.h internal.c \ + daemon.h daemon.c \ + stream.h stream.c \ + compression.h compression.c \ + session.h session.c \ + applicationlayer.c applicationlayer.h \ + alstructures.c alstructures.h + + +libmicrospdy_la_LDFLAGS = \ + $(SPDY_LIB_LDFLAGS) + +libmicrospdy_la_CFLAGS = -Wextra \ + $(SPDY_LIB_CFLAGS) + + +if USE_COVERAGE + AM_CFLAGS = --coverage +endif diff --git a/src/microspdy/alstructures.c b/src/microspdy/alstructures.c @@ -0,0 +1,41 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file alstructures.c + * @brief structures only for the application layer + * @author Andrey Uzunov + */ + +#include "platform.h" +#include "alstructures.h" +#include "internal.h" + +void +SPDY_destroy_request (struct SPDY_Request *request) +{ + if(NULL == request) + { + SPDYF_DEBUG("request is NULL"); + return; + } + //strings into request struct are just references to strings in + //headers, so no need to free them twice + SPDY_name_value_destroy(request->headers); + free(request); +} diff --git a/src/microspdy/alstructures.h b/src/microspdy/alstructures.h @@ -0,0 +1,79 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file alstructures.h + * @brief structures only for the application layer + * @author Andrey Uzunov + */ + +#ifndef ALSTRUCTURES_H +#define ALSTRUCTURES_H + +#include "platform.h" + + +/** + * Represents a SPDY request. + */ +struct SPDY_Request +{ + /** + * SPDY stream in whose context the request was received + */ + struct SPDYF_Stream *stream; + + /** + * Other HTTP headers from the request + */ + struct SPDY_NameValue *headers; + + /** + * HTTP method + */ + char *method; + + /** + * HTTP path + */ + char *path; + + /** + * HTTP version just like in HTTP request/response: + * "HTTP/1.0" or "HTTP/1.1" currently + */ + char *version; + + /** + * called host as in HTTP + */ + char *host; + + /** + * The scheme used ("http" or "https") + */ + char *scheme; + + /** + * Extra field to be used by the user with set/get func for whatever + * purpose he wants. + */ + void *user_cls; +}; + +#endif diff --git a/src/microspdy/applicationlayer.c b/src/microspdy/applicationlayer.c @@ -0,0 +1,679 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file applicationlayer.c + * @brief SPDY application or HTTP layer + * @author Andrey Uzunov + */ + +#include "platform.h" +#include "applicationlayer.h" +#include "alstructures.h" +#include "structures.h" +#include "internal.h" +#include "daemon.h" +#include "session.h" + + +/** + * Callback called when new stream is created. It extracts the info from + * the stream to create (HTTP) request object and pass it to the client. + * + * @param cls + * @param stream the new SPDY stream + * @return SPDY_YES on success, SPDY_NO on memomry error + */ +static int +spdy_handler_new_stream (void *cls, + struct SPDYF_Stream * stream) +{ + (void)cls; + uint i; + char *method = NULL; + char *path = NULL; + char *version = NULL; + char *host = NULL; + char *scheme = NULL; + struct SPDY_Request * request = NULL; + struct SPDY_NameValue * headers = NULL; + struct SPDY_NameValue * iterator = stream->headers; + struct SPDY_Daemon *daemon; + + daemon = stream->session->daemon; + + //if the user doesn't care, ignore it + if(NULL == daemon->new_request_cb) + return SPDY_YES; + + if(NULL == (headers=SPDY_name_value_create())) + goto free_and_fail; + + if(NULL==(request = malloc(sizeof(struct SPDY_Request)))) + goto free_and_fail; + + memset(request, 0, sizeof(struct SPDY_Request)); + request->stream = stream; + + /* extract the mandatory fields from stream->headers' structure + * to pass them to the client */ + while(iterator != NULL) + { + if(strcmp(":method",iterator->name) == 0) + { + if(1 != iterator->num_values) + break; + method = iterator->value[0]; + } + else if(strcmp(":path",iterator->name) == 0) + { + if(1 != iterator->num_values) + break; + path = iterator->value[0]; + } + else if(strcmp(":version",iterator->name) == 0) + { + if(1 != iterator->num_values) + break; + version = iterator->value[0]; + } + else if(strcmp(":host",iterator->name) == 0) + { + //TODO can it have more values? + if(1 != iterator->num_values) + break; + host = iterator->value[0]; + } + else if(strcmp(":scheme",iterator->name) == 0) + { + if(1 != iterator->num_values) + break; + scheme = iterator->value[0]; + } + else + for(i=0; i<iterator->num_values; ++i) + if (SPDY_YES != SPDY_name_value_add(headers,iterator->name,iterator->value[i])) + goto free_and_fail; + + iterator = iterator->next; + } + + request->method=method; + request->path=path; + request->version=version; + request->host=host; + request->scheme=scheme; + request->headers=headers; + + //check request validity, all these fields are mandatory for a request + if(NULL == method || strlen(method) == 0 + || NULL == path || strlen(path) == 0 + || NULL == version || strlen(version) == 0 + || NULL == host || strlen(host) == 0 + || NULL == scheme || strlen(scheme) == 0 + ) + { + //TODO HTTP 400 Bad Request must be answered + + SPDYF_DEBUG("Bad request"); + + SPDY_destroy_request(request); + + return SPDY_YES; + } + + //call client's callback function to notify + daemon->new_request_cb(daemon->cls, + request, + stream->priority, + method, + path, + version, + host, + scheme, + headers); + + return SPDY_YES; + + //for GOTO + free_and_fail: + + SPDY_name_value_destroy(headers); + return SPDY_NO; +} + + +/** + * Callback to be called when the response queue object was handled and + * the data was already sent or discarded. + * + * @param cls + * @param response_queue the object which is being handled + * @param status shows if actually the response was sent or it was + * discarded by the lib for any reason (e.g., closing session, + * closing stream, stopping daemon, etc.). It is possible that + * status indicates an error but parts of the response headers + * and/or body (in one + * or several frames) were already sent to the client. + */ +static void +spdy_handler_response_queue_result(void * cls, + struct SPDYF_Response_Queue *response_queue, + enum SPDY_RESPONSE_RESULT status) +{ + int streamopened; + struct SPDY_Request *request = (struct SPDY_Request *)cls; + + SPDYF_ASSERT(NULL == response_queue->data_frame + && NULL != response_queue->control_frame + || NULL != response_queue->data_frame + && NULL == response_queue->control_frame, + "response queue must have either control frame or data frame"); + + streamopened = !response_queue->stream->is_out_closed; + + response_queue->rrcb(response_queue->rrcb_cls, response_queue->response, request, status, streamopened); +} + + +int +SPDY_init () +{ + SPDYF_ASSERT(SPDYF_BUFFER_SIZE >= SPDY_MAX_SUPPORTED_FRAME_SIZE, + "Buffer size is less than max supported frame size!"); + SPDYF_ASSERT(SPDY_MAX_SUPPORTED_FRAME_SIZE >= 32, + "Max supported frame size must be bigger than the minimal value!"); + SPDYF_tls_global_init(); + return SPDY_YES; +} + + +void +SPDY_deinit () +{ + //currently nothing to be freed/deinited + //SPDYF_tls_global_deinit doesn't do anything now + //SPDYF_tls_global_deinit(); +} + + +void +SPDY_run (struct SPDY_Daemon *daemon) +{ + if(NULL == daemon) + { + SPDYF_DEBUG("daemon is NULL"); + return; + } + + SPDYF_run(daemon); +} + + +int +SPDY_get_timeout (struct SPDY_Daemon *daemon, + unsigned long long *timeout) +{ + if(NULL == daemon) + { + SPDYF_DEBUG("daemon is NULL"); + return SPDY_INPUT_ERROR; + } + + return SPDYF_get_timeout(daemon,timeout); +} + + +int +SPDY_get_fdset (struct SPDY_Daemon *daemon, + fd_set *read_fd_set, + fd_set *write_fd_set, + fd_set *except_fd_set) +{ + if(NULL == daemon + || NULL == read_fd_set + || NULL == write_fd_set + || NULL == except_fd_set) + { + SPDYF_DEBUG("a parameter is NULL"); + return SPDY_INPUT_ERROR; + } + + return SPDYF_get_fdset(daemon, + read_fd_set, + write_fd_set, + except_fd_set, + false); +} + + +struct SPDY_Daemon * +SPDY_start_daemon (uint16_t port, + const char *certfile, + const char *keyfile, + SPDY_NewSessionCallback nscb, + SPDY_SessionClosedCallback sccb, + SPDY_NewRequestCallback nrcb, + SPDY_NewPOSTDataCallback npdcb, + void * cls, + ...) +{ + struct SPDY_Daemon *daemon; + va_list valist; + + if(NULL == certfile) + { + SPDYF_DEBUG("certfile is NULL"); + return NULL; + } + if(NULL == keyfile) + { + SPDYF_DEBUG("keyfile is NULL"); + return NULL; + } + + va_start(valist, cls); + daemon = SPDYF_start_daemon_va ( port, + certfile, + keyfile, + nscb, + sccb, + nrcb, + npdcb, + &spdy_handler_new_stream, + cls, + NULL, + valist + ); + va_end(valist); + + return daemon; +} + + +void +SPDY_stop_daemon (struct SPDY_Daemon *daemon) +{ + if(NULL == daemon) + { + SPDYF_DEBUG("daemon is NULL"); + return; + } + + SPDYF_stop_daemon(daemon); +} + + +struct SPDY_Response * +SPDY_build_response(int status, + const char * statustext, + const char * version, + struct SPDY_NameValue * headers, + const void * data, + size_t size) +{ + struct SPDY_Response *response = NULL; + struct SPDY_NameValue ** all_headers = NULL; + char *fullstatus = NULL; + int ret; + int num_hdr_containers = 1; + + if(NULL == version) + { + SPDYF_DEBUG("version is NULL"); + return NULL; + } + + if(NULL == (response = malloc(sizeof(struct SPDY_Response)))) + goto free_and_fail; + memset(response, 0, sizeof(struct SPDY_Response)); + + if(NULL != headers) + num_hdr_containers = 2; + + if(NULL == (all_headers = malloc(num_hdr_containers * sizeof(struct SPDY_NameValue *)))) + goto free_and_fail; + memset(all_headers, 0, num_hdr_containers * sizeof(struct SPDY_NameValue *)); + + if(2 == num_hdr_containers) + all_headers[1] = headers; + + if(NULL == (all_headers[0] = SPDY_name_value_create())) + goto free_and_fail; + + if(NULL == statustext) + ret = asprintf(&fullstatus, "%i", status); + else + ret = asprintf(&fullstatus, "%i %s", status, statustext); + if(-1 == ret) + goto free_and_fail; + + if(SPDY_YES != SPDY_name_value_add(all_headers[0], ":status", fullstatus)) + goto free_and_fail; + + free(fullstatus); + fullstatus = NULL; + + if(SPDY_YES != SPDY_name_value_add(all_headers[0], ":version", version)) + goto free_and_fail; + + if(0 >= (response->headers_size = SPDYF_name_value_to_stream(all_headers, + num_hdr_containers, + &(response->headers)))) + goto free_and_fail; + + SPDY_name_value_destroy(all_headers[0]); + free(all_headers); + + if(size > 0) + { + //copy the data to the response object + if(NULL == (response->data = malloc(size))) + { + free(response->headers); + goto free_and_fail; + } + memcpy(response->data, data, size); + response->data_size = size; + } + + return response; + + //for GOTO + free_and_fail: + + free(fullstatus); + if(NULL != all_headers) + SPDY_name_value_destroy(all_headers[0]); + free(all_headers); + free(response); + + return NULL; +} + + +struct SPDY_Response * +SPDY_build_response_with_callback(int status, + const char * statustext, + const char * version, + struct SPDY_NameValue * headers, + SPDY_ResponseCallback rcb, + void *rcb_cls, + uint32_t block_size) +{ + struct SPDY_Response *response; + + if(NULL == rcb) + { + SPDYF_DEBUG("rcb is NULL"); + return NULL; + } + if(block_size > SPDY_MAX_SUPPORTED_FRAME_SIZE) + { + SPDYF_DEBUG("block_size is wrong"); + return NULL; + } + + if(0 == block_size) + block_size = SPDY_MAX_SUPPORTED_FRAME_SIZE; + + response = SPDY_build_response(status, + statustext, + version, + headers, + NULL, + 0); + + if(NULL == response) + { + return NULL; + } + + response->rcb = rcb; + response->rcb_cls = rcb_cls; + response->rcb_block_size = block_size; + + return response; +} + + +int +SPDY_queue_response (struct SPDY_Request * request, + struct SPDY_Response *response, + bool closestream, + bool consider_priority, + SPDY_ResponseResultCallback rrcb, + void * rrcb_cls) +{ + struct SPDYF_Response_Queue *headers_to_queue; + struct SPDYF_Response_Queue *body_to_queue; + SPDYF_ResponseQueueResultCallback frqcb = NULL; + void *frqcb_cls = NULL; + int int_consider_priority = consider_priority ? SPDY_YES : SPDY_NO; + + if(NULL == request) + { + SPDYF_DEBUG("request is NULL"); + return SPDY_INPUT_ERROR; + } + if(NULL == response) + { + SPDYF_DEBUG("request is NULL"); + return SPDY_INPUT_ERROR; + } + + if(request->stream->is_out_closed + || SPDY_SESSION_STATUS_CLOSING == request->stream->session->status) + return SPDY_NO; + + if(NULL != rrcb) + { + frqcb_cls = request; + frqcb = &spdy_handler_response_queue_result; + } + + if(response->data_size > 0) + { + //SYN_REPLY and DATA will be queued + + if(NULL == (headers_to_queue = SPDYF_response_queue_create(false, + response->headers, + response->headers_size, + response, + request->stream, + false, + NULL, + NULL, + NULL, + NULL))) + { + return SPDY_NO; + } + + if(NULL == (body_to_queue = SPDYF_response_queue_create(true, + response->data, + response->data_size, + response, + request->stream, + closestream, + frqcb, + frqcb_cls, + rrcb, + rrcb_cls))) + { + SPDYF_response_queue_destroy(headers_to_queue); + return SPDY_NO; + } + + SPDYF_queue_response (headers_to_queue, + request->stream->session, + int_consider_priority); + + SPDYF_queue_response (body_to_queue, + request->stream->session, + int_consider_priority); + } + else if(NULL == response->rcb) + { + //no "body" will be queued, e.g. HTTP 404 without body + + if(NULL == (headers_to_queue = SPDYF_response_queue_create(false, + response->headers, + response->headers_size, + response, + request->stream, + closestream, + frqcb, + frqcb_cls, + rrcb, + rrcb_cls))) + { + return SPDY_NO; + } + + SPDYF_queue_response (headers_to_queue, + request->stream->session, + int_consider_priority); + } + else + { + //response with callbacks + + if(NULL == (headers_to_queue = SPDYF_response_queue_create(false, + response->headers, + response->headers_size, + response, + request->stream, + false, + NULL, + NULL, + NULL, + NULL))) + { + return SPDY_NO; + } + + if(NULL == (body_to_queue = SPDYF_response_queue_create(true, + response->data, + response->data_size, + response, + request->stream, + closestream, + frqcb, + frqcb_cls, + rrcb, + rrcb_cls))) + { + SPDYF_response_queue_destroy(headers_to_queue); + return SPDY_NO; + } + + SPDYF_queue_response (headers_to_queue, + request->stream->session, + int_consider_priority); + + SPDYF_queue_response (body_to_queue, + request->stream->session, + int_consider_priority); + } + + return SPDY_YES; +} + + +socklen_t +SPDY_get_remote_addr(struct SPDY_Session * session, + struct sockaddr ** addr) +{ + if(NULL == session) + { + SPDYF_DEBUG("session is NULL"); + return 0; + } + + *addr = session->addr; + + return session->addr_len; +} + + +struct SPDY_Session * +SPDY_get_session_for_request(const struct SPDY_Request * request) +{ + if(NULL == request) + { + SPDYF_DEBUG("request is NULL"); + return NULL; + } + + return request->stream->session; +} + + +void * +SPDY_get_cls_from_session(struct SPDY_Session * session) +{ + if(NULL == session) + { + SPDYF_DEBUG("session is NULL"); + return NULL; + } + + return session->user_cls; +} + + +void +SPDY_set_cls_to_session(struct SPDY_Session * session, + void * cls) +{ + if(NULL == session) + { + SPDYF_DEBUG("session is NULL"); + return; + } + + session->user_cls = cls; +} + + +void * +SPDY_get_cls_from_request(struct SPDY_Request * request) +{ + if(NULL == request) + { + SPDYF_DEBUG("request is NULL"); + return NULL; + } + + return request->user_cls; +} + + +void +SPDY_set_cls_to_request(struct SPDY_Request * request, + void * cls) +{ + if(NULL == request) + { + SPDYF_DEBUG("request is NULL"); + return; + } + + request->user_cls = cls; +} diff --git a/src/microspdy/applicationlayer.h b/src/microspdy/applicationlayer.h @@ -0,0 +1,31 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file applicationlayer.h + * @brief SPDY application or HTTP layer + * @author Andrey Uzunov + */ + +#ifndef APPLICATIONLAYER_H +#define APPLICATIONLAYER_H + +#include "platform.h" + + +#endif diff --git a/src/microspdy/compression.c b/src/microspdy/compression.c @@ -0,0 +1,441 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file compression.c + * @brief zlib handling functions + * @author Andrey Uzunov + */ + +#include "platform.h" +#include "structures.h" +#include "internal.h" +#include "compression.h" + +/* spdy ver 3 specific dictionary used by zlib */ +static const unsigned char +spdyf_zlib_dictionary[] = { + 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // - - - - o p t i + 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // o n s - - - - h + 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // e a d - - - - p + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // o s t - - - - p + 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // u t - - - - d e + 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // l e t e - - - - + 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // t r a c e - - - + 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // - a c c e p t - + 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p + 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t - c h a r s e + 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t - - - - a c c + 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e p t - e n c o + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // d i n g - - - - + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // a c c e p t - l + 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // a n g u a g e - + 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p + 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t - r a n g e s + 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // - - - - a g e - + 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // - - - a l l o w + 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // - - - - a u t h + 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // o r i z a t i o + 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n - - - - c a c + 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // h e - c o n t r + 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // o l - - - - c o + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // n n e c t i o n + 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t + 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // e n t - b a s e + 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t + 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e n t - e n c o + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // d i n g - - - - + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // c o n t e n t - + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // l a n g u a g e + 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t + 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // e n t - l e n g + 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // t h - - - - c o + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // n t e n t - l o + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // c a t i o n - - + 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n + 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t - m d 5 - - - + 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // - c o n t e n t + 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // - r a n g e - - + 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n + 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t - t y p e - - + 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // - - d a t e - - + 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // - - e t a g - - + 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // - - e x p e c t + 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // - - - - e x p i + 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // r e s - - - - f + 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // r o m - - - - h + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // o s t - - - - i + 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f - m a t c h - + 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // - - - i f - m o + 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // d i f i e d - s + 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // i n c e - - - - + 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // i f - n o n e - + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // m a t c h - - - + 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // - i f - r a n g + 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e - - - - i f - + 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // u n m o d i f i + 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // e d - s i n c e + 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // - - - - l a s t + 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // - m o d i f i e + 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d - - - - l o c + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // a t i o n - - - + 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // - m a x - f o r + 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // w a r d s - - - + 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // - p r a g m a - + 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // - - - p r o x y + 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // - a u t h e n t + 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // i c a t e - - - + 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // - p r o x y - a + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // u t h o r i z a + 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // t i o n - - - - + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // r a n g e - - - + 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // - r e f e r e r + 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // - - - - r e t r + 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y - a f t e r - + 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // - - - s e r v e + 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r - - - - t e - + 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // - - - t r a i l + 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // e r - - - - t r + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // a n s f e r - e + 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // n c o d i n g - + 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // - - - u p g r a + 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // d e - - - - u s + 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // e r - a g e n t + 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // - - - - v a r y + 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // - - - - v i a - + 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // - - - w a r n i + 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // n g - - - - w w + 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w - a u t h e n + 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // t i c a t e - - + 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // - - m e t h o d + 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // - - - - g e t - + 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // - - - s t a t u + 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s - - - - 2 0 0 + 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // - O K - - - - v + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // e r s i o n - - + 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // - - H T T P - 1 + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // - 1 - - - - u r + 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l - - - - p u b + 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // l i c - - - - s + 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // e t - c o o k i + 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e - - - - k e e + 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p - a l i v e - + 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // - - - o r i g i + 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n 1 0 0 1 0 1 2 + 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 0 1 2 0 2 2 0 5 + 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 2 0 6 3 0 0 3 0 + 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 2 3 0 3 3 0 4 3 + 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 0 5 3 0 6 3 0 7 + 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 4 0 2 4 0 5 4 0 + 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 6 4 0 7 4 0 8 4 + 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 0 9 4 1 0 4 1 1 + 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 4 1 2 4 1 3 4 1 + 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 4 4 1 5 4 1 6 4 + 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 1 7 5 0 2 5 0 4 + 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 5 0 5 2 0 3 - N + 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // o n - A u t h o + 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // r i t a t i v e + 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // - I n f o r m a + 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // t i o n 2 0 4 - + 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // N o - C o n t e + 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // n t 3 0 1 - M o + 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // v e d - P e r m + 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // a n e n t l y 4 + 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 0 0 - B a d - R + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // e q u e s t 4 0 + 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1 - U n a u t h + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // o r i z e d 4 0 + 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3 - F o r b i d + 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // d e n 4 0 4 - N + 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // o t - F o u n d + 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 5 0 0 - I n t e + 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // r n a l - S e r + 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // v e r - E r r o + 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r 5 0 1 - N o t + 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // - I m p l e m e + 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // n t e d 5 0 3 - + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // S e r v i c e - + 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // U n a v a i l a + 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // b l e J a n - F + 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // e b - M a r - A + 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // p r - M a y - J + 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // u n - J u l - A + 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // u g - S e p t - + 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // O c t - N o v - + 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // D e c - 0 0 - 0 + 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0 - 0 0 - M o n + 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // - - T u e - - W + 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // e d - - T h u - + 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // - F r i - - S a + 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t - - S u n - - + 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // G M T c h u n k + 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // e d - t e x t - + 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // h t m l - i m a + 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // g e - p n g - i + 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // m a g e - j p g + 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // - i m a g e - g + 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // i f - a p p l i + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x + 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // m l - a p p l i + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x + 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // h t m l - x m l + 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // - t e x t - p l + 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // a i n - t e x t + 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // - j a v a s c r + 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // i p t - p u b l + 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // i c p r i v a t + 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // e m a x - a g e + 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // - g z i p - d e + 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // f l a t e - s d + 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // c h c h a r s e + 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t - u t f - 8 c + 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // h a r s e t - i + 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // s o - 8 8 5 9 - + 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1 - u t f - - - + 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // - e n q - 0 - +}; + + +int +SPDYF_zlib_deflate_init(z_stream *strm) +{ + int ret; + + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; + strm->opaque = Z_NULL; + //the second argument is "level of compression" + //use 0 for no compression; 9 for best compression + ret = deflateInit(strm, Z_DEFAULT_COMPRESSION); + if(ret != Z_OK) + { + SPDYF_DEBUG("deflate init"); + return SPDY_NO; + } + ret = deflateSetDictionary(strm, + spdyf_zlib_dictionary, + sizeof(spdyf_zlib_dictionary)); + if(ret != Z_OK) + { + SPDYF_DEBUG("deflate set dict"); + deflateEnd(strm); + return SPDY_NO; + } + return SPDY_YES; +} + + +void +SPDYF_zlib_deflate_end(z_stream *strm) +{ + deflateEnd(strm); +} + +int +SPDYF_zlib_deflate(z_stream *strm, + const void *src, + size_t src_size, + size_t *data_used, + void **dest, + size_t *dest_size) +{ + int ret; + int flush; + uint have; + Bytef out[SPDYF_ZLIB_CHUNK]; + + *dest = NULL; + *dest_size = 0; + + do + { + /* check for big data bigger than the buffer used */ + if(src_size > SPDYF_ZLIB_CHUNK) + { + strm->avail_in = SPDYF_ZLIB_CHUNK; + src_size -= SPDYF_ZLIB_CHUNK; + /* flush is used for the loop to detect if we still + * need to supply additional + * data to the stream via avail_in and next_in. */ + flush = Z_NO_FLUSH; + } + else + { + strm->avail_in = src_size; + flush = Z_SYNC_FLUSH; + } + *data_used += strm->avail_in; + + strm->next_in = (Bytef *)src; + + /* Loop while output data is available */ + do + { + strm->avail_out = SPDYF_ZLIB_CHUNK; + strm->next_out = out; + + /* No need to check return value of deflate. + * (See zlib documentation at http://www.zlib.net/zlib_how.html */ + ret = deflate(strm, flush); + have = SPDYF_ZLIB_CHUNK - strm->avail_out; + + /* (Re)allocate memory for dest and keep track of it's size. */ + *dest_size += have; + *dest = realloc(*dest, *dest_size); + if(!*dest) + { + SPDYF_DEBUG("realloc data for result"); + deflateEnd(strm); + return SPDY_NO; + } + memcpy((*dest) + ((*dest_size) - have), out, have); + } + while(strm->avail_out == 0); + /* At this point, all of the input data should already + * have been used. */ + SPDYF_ASSERT(strm->avail_in == 0,"compressing bug"); + } + while(flush != Z_SYNC_FLUSH); + + return Z_OK == ret ? SPDY_YES : SPDY_NO; +} + + +int +SPDYF_zlib_inflate_init(z_stream *strm) +{ + int ret; + + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; + strm->opaque = Z_NULL; + strm->avail_in = 0; + strm->next_in = Z_NULL; + //change 15 to lower value for performance and benchmark + //"The windowBits parameter is the base two logarithm of the + // maximum window size (the size of the history buffer)." + ret = inflateInit2(strm, 15); + if(ret != Z_OK) + { + SPDYF_DEBUG("Cannot inflateInit2 the stream"); + return SPDY_NO; + } + return SPDY_YES; +} + + +void +SPDYF_zlib_inflate_end(z_stream *strm) +{ + inflateEnd(strm); +} + + +int +SPDYF_zlib_inflate(z_stream *strm, + const void *src, + size_t src_size, + void **dest, + size_t *dest_size) +{ + int ret = Z_OK; + uint32_t have; + Bytef out[SPDYF_ZLIB_CHUNK]; + + *dest = NULL; + *dest_size = 0; + + /* decompress until deflate stream ends or end of file */ + do + { + if(src_size > SPDYF_ZLIB_CHUNK) + { + strm->avail_in = SPDYF_ZLIB_CHUNK; + src_size -= SPDYF_ZLIB_CHUNK; + } + else + { + strm->avail_in = src_size; + src_size = 0; + } + + if(strm->avail_in == 0){ + //the loop breaks always here as the stream never ends + break; + } + + strm->next_in = (Bytef *) src; + /* run inflate() on input until output buffer not full */ + do { + strm->avail_out = SPDYF_ZLIB_CHUNK; + strm->next_out = out; + ret = inflate(strm, Z_SYNC_FLUSH); + + switch (ret) + { + case Z_STREAM_ERROR: + SPDYF_DEBUG("Error on inflate"); + //no inflateEnd here, same in zlib example + return SPDY_NO; + + case Z_NEED_DICT: + ret = inflateSetDictionary(strm, + spdyf_zlib_dictionary, + sizeof(spdyf_zlib_dictionary)); + if(ret != Z_OK) + { + SPDYF_DEBUG("Error on inflateSetDictionary"); + inflateEnd(strm); + return SPDY_NO; + } + ret = inflate(strm, Z_SYNC_FLUSH); + if(Z_STREAM_ERROR == ret) + { + SPDYF_DEBUG("Error on inflate"); + return SPDY_NO; + } + break; + + case Z_DATA_ERROR: + SPDYF_DEBUG("Z_DATA_ERROR"); + inflateEnd(strm); + return SPDY_NO; + + case Z_MEM_ERROR: + SPDYF_DEBUG("Z_MEM_ERROR"); + inflateEnd(strm); + return SPDY_NO; + } + have = SPDYF_ZLIB_CHUNK - strm->avail_out; + *dest_size += have; + /* (re)alloc memory for the output buffer */ + *dest = realloc(*dest, *dest_size); + if(!*dest) + { + SPDYF_DEBUG("Cannot realloc memory"); + inflateEnd(strm); + return SPDY_NO; + } + memcpy((*dest) + ((*dest_size) - have), out, have); + } + while (0 == strm->avail_out); + } + while (Z_STREAM_END != ret); + + return Z_OK == ret || Z_STREAM_END == ret ? SPDY_YES : SPDY_NO; +} diff --git a/src/microspdy/compression.h b/src/microspdy/compression.h @@ -0,0 +1,117 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file compression.h + * @brief zlib handling functions + * @author Andrey Uzunov + */ + +#ifndef COMPRESSION_H +#define COMPRESSION_H + +#include "platform.h" + +/* size of buffers used by zlib on (de)compressing */ +#define SPDYF_ZLIB_CHUNK 16384 + + +/** + * Initializes the zlib stream for compression. Must be called once + * for a session on initialization. + * + * @param strm Zlib stream on which we work + * @return SPDY_NO if zlib failed. SPDY_YES otherwise + */ +int +SPDYF_zlib_deflate_init(z_stream *strm); + + +/** + * Deinitializes the zlib stream for compression. Should be called once + * for a session on cleaning up. + * + * @param strm Zlib stream on which we work + */ +void +SPDYF_zlib_deflate_end(z_stream *strm); + + +/** + * Compressing stream with zlib. + * + * @param strm Zlib stream on which we work + * @param src stream of the data to be compressed + * @param src_size size of the data + * @param data_used the number of bytes from src_stream that were used + * TODO do we need + * @param dest the resulting compressed stream. Should be NULL. Must be + * freed later manually. + * @param dest_size size of the data after compression + * @return SPDY_NO if malloc or zlib failed. SPDY_YES otherwise + */ +int +SPDYF_zlib_deflate(z_stream *strm, + const void *src, + size_t src_size, + size_t *data_used, + void **dest, + size_t *dest_size); + + +/** + * Initializes the zlib stream for decompression. Must be called once + * for a session. + * + * @param strm Zlib stream on which we work + * @return SPDY_NO if zlib failed. SPDY_YES otherwise + */ +int +SPDYF_zlib_inflate_init(z_stream *strm); + + +/** + * Deinitializes the zlib stream for decompression. Should be called once + * for a session on cleaning up. + * + * @param strm Zlib stream on which we work + */ +void +SPDYF_zlib_inflate_end(z_stream *strm); + + +/** + * Decompressing stream with zlib. + * + * @param strm Zlib stream on which we work + * @param src stream of the data to be decompressed + * @param src_size size of the data + * @param dest the resulting decompressed stream. Should be NULL. Must + * be freed manually. + * @param dest_size size of the data after decompression + * @return SPDY_NO if malloc or zlib failed. SPDY_YES otherwise. If the + * function fails, the SPDY session must be closed + */ +int +SPDYF_zlib_inflate(z_stream *strm, + const void *src, + size_t src_size, + void **dest, + size_t *dest_size); + +#endif diff --git a/src/microspdy/daemon.c b/src/microspdy/daemon.c @@ -0,0 +1,515 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file daemon.c + * @brief daemon functionality + * @author Andrey Uzunov + */ + +#include "platform.h" +#include "structures.h" +#include "internal.h" +#include "session.h" +#include "tls.h" + + +/** + * Default implementation of the panic function, + * prints an error message and aborts. + * + * @param cls unused + * @param file name of the file with the problem + * @param line line number with the problem + * @param reason error message with details + */ +static void +spdyf_panic_std (void *cls, + const char *file, + unsigned int line, + const char *reason) +{ + (void)cls; + fprintf (stdout, "Fatal error in libmicrospdy %s:%u: %s\n", + file, line, reason); + //raise(SIGINT); //used for gdb + abort (); +} + + +/** + * Global handler for fatal errors. + */ +SPDY_PanicCallback spdyf_panic = &spdyf_panic_std; + + +/** + * Global closure argument for "spdyf_panic". + */ +void *spdyf_panic_cls; + + +/** + * Free resources associated with all closed connections. + * (destroy responses, free buffers, etc.). + * + * @param daemon daemon to clean up + */ +static void +spdyf_cleanup_sessions (struct SPDY_Daemon *daemon) +{ + struct SPDY_Session *session; + + while (NULL != (session = daemon->cleanup_head)) + { + DLL_remove (daemon->cleanup_head, + daemon->cleanup_tail, + session); + + SPDYF_session_destroy(session); + } +} + + +/** + * Closing of all connections handled by the daemon. + * + * @param daemon SPDY daemon + */ +static void +spdyf_close_all_sessions (struct SPDY_Daemon *daemon) +{ + struct SPDY_Session *session; + + while (NULL != (session = daemon->sessions_head)) + { + //prepare GOAWAY frame + SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_OK, true); + //try to send the frame (it is best effort, so it will maybe sent) + SPDYF_session_write(session,true); + SPDYF_session_close(session); + } + + spdyf_cleanup_sessions(daemon); +} + + +/** + * Parse a list of options given as varargs. + * + * @param daemon the daemon to initialize + * @param valist the options + * @return SPDY_YES on success, SPDY_NO on error + */ +static int +spdyf_parse_options_va (struct SPDY_Daemon *daemon, + va_list valist) +{ + enum SPDY_DAEMON_OPTION opt; + + while (SPDY_DAEMON_OPTION_END != (opt = (enum SPDY_DAEMON_OPTION) va_arg (valist, int))) + { + if(opt & daemon->options) + { + SPDYF_DEBUG("Daemon option %i used twice",opt); + return SPDY_NO; + } + daemon->options |= opt; + + switch (opt) + { + case SPDY_DAEMON_OPTION_SESSION_TIMEOUT: + daemon->session_timeout = va_arg (valist, unsigned int); + break; + case SPDY_DAEMON_OPTION_SOCK_ADDR: + daemon->address = va_arg (valist, struct sockaddr *); + break; + case SPDY_DAEMON_OPTION_FLAGS: + daemon->flags = va_arg (valist, enum SPDY_DAEMON_FLAG); + break; + default: + SPDYF_DEBUG("Wrong option for the daemon %i",opt); + return SPDY_NO; + } + } + return SPDY_YES; +} + + +void +SPDY_set_panic_func (SPDY_PanicCallback cb, + void *cls) +{ + spdyf_panic = cb; + spdyf_panic_cls = cls; +} + + +struct SPDY_Daemon * +SPDYF_start_daemon_va (uint16_t port, + const char *certfile, + const char *keyfile, + SPDY_NewSessionCallback nscb, + SPDY_SessionClosedCallback sccb, + SPDY_NewRequestCallback nrcb, + SPDY_NewPOSTDataCallback npdcb, + SPDYF_NewStreamCallback fnscb, + void * cls, + void * fcls, + va_list valist) +{ + struct SPDY_Daemon *daemon = NULL; + int afamily; + int option_on = 1; + int ret; + struct sockaddr_in* servaddr4 = NULL; +#if HAVE_INET6 + struct sockaddr_in6* servaddr6 = NULL; +#endif + socklen_t addrlen; + + if (NULL == (daemon = malloc (sizeof (struct SPDY_Daemon)))) + { + SPDYF_DEBUG("malloc"); + return NULL; + } + memset (daemon, 0, sizeof (struct SPDY_Daemon)); + daemon->socket_fd = -1; + daemon->port = port; + if (NULL == (daemon->certfile = strdup (certfile))) + { + SPDYF_DEBUG("str"); + goto free_and_fail; + } + if (NULL == (daemon->keyfile = strdup (keyfile))) + { + SPDYF_DEBUG("str"); + goto free_and_fail; + } + daemon->new_session_cb = nscb; + daemon->session_closed_cb = sccb; + daemon->new_request_cb = nrcb; + daemon->new_post_data_cb = npdcb; + daemon->cls = cls; + daemon->fcls = fcls; + daemon->fnew_stream_cb = fnscb; + + if(SPDY_YES != spdyf_parse_options_va (daemon, valist)) + { + SPDYF_DEBUG("parse"); + goto free_and_fail; + } + + if(!port && NULL == daemon->address) + { + SPDYF_DEBUG("Port is 0"); + goto free_and_fail; + } + +#if HAVE_INET6 + //handling IPv6 + if((daemon->flags & SPDY_DAEMON_FLAG_ONLY_IPV6) + && NULL != daemon->address && AF_INET6 != daemon->address->sa_family) + { + SPDYF_DEBUG("SPDY_DAEMON_FLAG_ONLY_IPV6 set but IPv4 address provided"); + goto free_and_fail; + } + + if(NULL == daemon->address) + { + addrlen = sizeof (struct sockaddr_in6); + + if (NULL == (servaddr6 = malloc (addrlen))) + { + SPDYF_DEBUG("malloc"); + goto free_and_fail; + } + memset (servaddr6, 0, addrlen); + servaddr6->sin6_family = AF_INET6; + servaddr6->sin6_addr = in6addr_any; + servaddr6->sin6_port = htons (port); + daemon->address = (struct sockaddr *) servaddr6; + } + + afamily = AF_INET6 == daemon->address->sa_family ? PF_INET6 : PF_INET; +#else + //handling IPv4 + if(daemon->flags & SPDY_DAEMON_FLAG_ONLY_IPV6) + { + SPDYF_DEBUG("SPDY_DAEMON_FLAG_ONLY_IPV6 set but no support"); + goto free_and_fail; + } + + if(NULL == daemon->address) + { + addrlen = sizeof (struct sockaddr_in); + + if (NULL == (servaddr4 = malloc (addrlen))) + { + SPDYF_DEBUG("malloc"); + goto free_and_fail; + } + memset (servaddr4, 0, addrlen); + servaddr4->sin_family = AF_INET; + servaddr4->sin_addr = INADDR_ANY; + servaddr4->sin_port = htons (port); + daemon->address = (struct sockaddr *) servaddr4; + } + + afamily = PF_INET; +#endif + + daemon->socket_fd = socket (afamily, SOCK_STREAM, 0); + if (-1 == daemon->socket_fd) + { + SPDYF_DEBUG("sock"); + goto free_and_fail; + } + + //setting option for the socket to reuse address + ret = setsockopt(daemon->socket_fd, SOL_SOCKET, SO_REUSEADDR, &option_on, sizeof(option_on)); + if(ret) + { + SPDYF_DEBUG("WARNING: SO_REUSEADDR was not set for the server"); + } + +#if HAVE_INET6 + if(daemon->flags & SPDY_DAEMON_FLAG_ONLY_IPV6) + { + ret = setsockopt(daemon->socket_fd, IPPROTO_IPV6, IPV6_V6ONLY, &option_on, sizeof(option_on)); + if(ret) + { + SPDYF_DEBUG("setsockopt with IPPROTO_IPV6 failed"); + goto free_and_fail; + } + } +#endif + + if (-1 == bind (daemon->socket_fd, daemon->address, addrlen)) + { + SPDYF_DEBUG("bind %i",errno); + goto free_and_fail; + } + + if (listen (daemon->socket_fd, 20) < 0) + { + SPDYF_DEBUG("listen %i",errno); + goto free_and_fail; + } + + if(SPDY_YES != SPDYF_tls_init(daemon)) + { + SPDYF_DEBUG("tls"); + goto free_and_fail; + } + + return daemon; + + //for GOTO + free_and_fail: + if(daemon->socket_fd > 0) + close (daemon->socket_fd); + + free(servaddr4); +#if HAVE_INET6 + free(servaddr6); +#endif + if(NULL != daemon->certfile) + free(daemon->certfile); + if(NULL != daemon->keyfile) + free(daemon->keyfile); + free (daemon); + + return NULL; +} + + +void +SPDYF_stop_daemon (struct SPDY_Daemon *daemon) +{ + SPDYF_tls_deinit(daemon); + + shutdown (daemon->socket_fd, SHUT_RDWR); + spdyf_close_all_sessions (daemon); + close (daemon->socket_fd); + + if(!(SPDY_DAEMON_OPTION_SOCK_ADDR & daemon->options)) + free(daemon->address); + + free(daemon->certfile); + free(daemon->keyfile); + + free(daemon); +} + + +int +SPDYF_get_timeout (struct SPDY_Daemon *daemon, + unsigned long long *timeout) +{ + time_t earliest_deadline = 0; + time_t now; + struct SPDY_Session *pos; + bool have_timeout; + + if(0 == daemon->session_timeout) + return SPDY_NO; + + now = SPDYF_monotonic_time(); + have_timeout = false; + for (pos = daemon->sessions_head; NULL != pos; pos = pos->next) + { + if ( (! have_timeout) || + (earliest_deadline > pos->last_activity + daemon->session_timeout) ) + earliest_deadline = pos->last_activity + daemon->session_timeout; + + have_timeout = true; + + if (SPDY_YES == SPDYF_tls_is_pending(pos)) + { + earliest_deadline = 0; + break; + } + } + + if (!have_timeout) + return SPDY_NO; + if (earliest_deadline < now) + *timeout = 0; + else + //*timeout = 1000 * (1 + earliest_deadline - now); + *timeout = earliest_deadline - now; + + return SPDY_YES; +} + + +int +SPDYF_get_fdset (struct SPDY_Daemon *daemon, + fd_set *read_fd_set, + fd_set *write_fd_set, + fd_set *except_fd_set, + bool all) +{ + (void)except_fd_set; + struct SPDY_Session *pos; + int fd; + int max_fd = -1; + + fd = daemon->socket_fd; + if (-1 != fd) + { + FD_SET (fd, read_fd_set); + /* update max file descriptor */ + max_fd = fd; + } + + for (pos = daemon->sessions_head; NULL != pos; pos = pos->next) + { + fd = pos->socket_fd; + FD_SET(fd, read_fd_set); + if(all + || NULL != pos->response_queue_head //frames pending + || NULL != pos->write_buffer //part of last frame pending + || SPDY_SESSION_STATUS_CLOSING == pos->status //the session is about to be closed + || daemon->session_timeout //timeout passed for the session + && (pos->last_activity + daemon->session_timeout < SPDYF_monotonic_time()) + || SPDY_YES == SPDYF_tls_is_pending(pos) //data in TLS' read buffer pending + || ((pos->read_buffer_offset - pos->read_buffer_beginning) > 0) // data in lib's read buffer pending + ) + FD_SET(fd, write_fd_set); + if(fd > max_fd) + max_fd = fd; + } + + return max_fd; +} + + +void +SPDYF_run (struct SPDY_Daemon *daemon) +{ + struct SPDY_Session *pos; + struct SPDY_Session *next; + int num_ready; + fd_set rs; + fd_set ws; + fd_set es; + int max; + struct timeval timeout; + int ds; + + timeout.tv_sec = 0; + timeout.tv_usec = 0; + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + //here we need really all descriptors to see later which are ready + max = SPDYF_get_fdset(daemon,&rs,&ws,&es, true); + + num_ready = select (max + 1, &rs, &ws, &es, &timeout); + + if(num_ready < 1) + return; + + if ( (-1 != (ds = daemon->socket_fd)) && + (FD_ISSET (ds, &rs)) ){ + SPDYF_session_accept(daemon); + } + + next = daemon->sessions_head; + while (NULL != (pos = next)) + { + next = pos->next; + ds = pos->socket_fd; + if (ds != -1) + { + //fill the read buffer + if (FD_ISSET (ds, &rs) || SPDYF_tls_is_pending(pos)){ + SPDYF_session_read(pos); + } + + //do something with the data in read buffer + if(SPDY_NO == SPDYF_session_idle(pos)) + { + //the session was closed, cannot write anymore + //continue; + } + + //write whatever has been put to the response queue + //during read or idle operation, something might be put + //on the response queue, thus call write operation + if (FD_ISSET (ds, &ws)){ + if(SPDY_NO == SPDYF_session_write(pos, false)) + { + //SPDYF_session_close(pos); + //continue; + } + } + + /* the response queue has been flushed for half closed + * connections, so let close them */ + /*if(pos->read_closed) + { + SPDYF_session_close(pos); + }*/ + } + } + + spdyf_cleanup_sessions(daemon); +} diff --git a/src/microspdy/daemon.h b/src/microspdy/daemon.h @@ -0,0 +1,121 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file daemon.h + * @brief daemon functionality + * @author Andrey Uzunov + */ + +#ifndef DAEMON_H +#define DAEMON_H + +#include "platform.h" + + +/** + * Start a SPDDY webserver on the given port. + * + * @param port port to bind to + * @param certfile path to the certificate that will be used by server + * @param keyfile path to the keyfile for the certificate + * @param nscb callback called when a new SPDY session is + * established by a client + * @param sccb callback called when a client closes the session + * @param nrcb callback called when a client sends request + * @param npdcb callback called when HTTP POST params are received + * after request + * @param fnscb callback called when new stream is opened by a client + * @param cls extra argument to all of the callbacks without those + * specific only for the framing layer + * @param fcls extra argument to all of the callbacks, specific only for + * the framing layer (those vars starting with 'f'). + * @param valist va_list of options (type-value pairs, + * terminated with SPDY_DAEMON_OPTION_END). + * @return NULL on error, handle to daemon on success + */ +struct SPDY_Daemon * +SPDYF_start_daemon_va (uint16_t port, + const char *certfile, + const char *keyfile, + SPDY_NewSessionCallback nscb, + SPDY_SessionClosedCallback sccb, + SPDY_NewRequestCallback nrcb, + SPDY_NewPOSTDataCallback npdcb, + SPDYF_NewStreamCallback fnscb, + void * cls, + void * fcls, + va_list valist); + + +/** + * Run webserver operations (without blocking unless + * in client callbacks). This method must be called in the client event + * loop. + * + * @param daemon daemon to run + */ +void +SPDYF_run (struct SPDY_Daemon *daemon); + + +/** + * Obtain timeout value for select for this daemon. The returned value + * is how long select + * should at most block, not the timeout value set for connections. + * + * @param daemon daemon to query for timeout + * @param timeout set to the timeout (in seconds) + * @return SPDY_YES on success, SPDY_NO if no connections exist that + * would necessiate the use of a timeout right now + */ +int +SPDYF_get_timeout (struct SPDY_Daemon *daemon, + unsigned long long *timeout); + + +/** + * Obtain the select sets for this daemon. The idea of SPDYF_get_fdset + * is to return such descriptors that the select in the application can + * return and SPDY_run can be called only when this is really needed. + * That means not all sockets will be added to write_fd_set. + * + * @param daemon daemon to get sets from + * @param read_fd_set read set + * @param write_fd_set write set + * @param except_fd_set except set + * @param all add all session's descriptors to write_fd_set or not + * @return largest FD added + */ +int +SPDYF_get_fdset (struct SPDY_Daemon *daemon, + fd_set *read_fd_set, + fd_set *write_fd_set, + fd_set *except_fd_set, + bool all); + + +/** + * Shutdown the daemon. + * + * @param daemon daemon to stop + */ +void +SPDYF_stop_daemon (struct SPDY_Daemon *daemon); + +#endif diff --git a/src/microspdy/internal.c b/src/microspdy/internal.c @@ -0,0 +1,38 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file internal.c + * @brief internal functions and macros for the framing layer + * @author Andrey Uzunov + */ + +#include "platform.h" +#include "structures.h" + + +time_t +SPDYF_monotonic_time(void) +{ +#ifdef HAVE_CLOCK_GETTIME + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) + return ts.tv_sec; +#endif + return time(NULL); +} diff --git a/src/microspdy/internal.h b/src/microspdy/internal.h @@ -0,0 +1,189 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file structures.h + * @brief internal functions and macros for the framing layer + * @author Andrey Uzunov + */ + +#ifndef INTERNAL_H_H +#define INTERNAL_H_H + +#include "platform.h" +#include "microspdy.h" +#include "tls.h" + +/* size of read buffers for each connection + * must be at least the size of SPDY_MAX_SUPPORTED_FRAME_SIZE */ +#define SPDYF_BUFFER_SIZE 8192 + +/* number of frames written to the socket at once. After X frames + * everything should be run again. In this way the application can + * response to more important requests while a big file is still + * being transmitted to the client */ +#define SPDYF_NUM_SENT_FRAMES_AT_ONCE 10 + + +/** + * Handler for fatal errors. + */ +extern SPDY_PanicCallback spdyf_panic; + + +/** + * Closure argument for "mhd_panic". + */ +extern void *spdyf_panic_cls; + + +/** + * Trigger 'panic' action based on fatal errors. + * + * @param msg error message (const char *) + */ +#define SPDYF_PANIC(msg) \ + spdyf_panic (spdyf_panic_cls, __FILE__, __LINE__, msg) + + +/** + * Asserts the validity of an expression. + * + * @param expression (bool) + * @param msg message to print on error (const char *) + */ +#define SPDYF_ASSERT(expr,msg) \ + if(!(expr)){\ + SPDYF_PANIC(msg);\ + abort();\ + } + + +/** + * Convert 24 bit integer from host byte order to network byte order. + * + * @param n input value (int32_t) + * @return converted value (uint32_t) + */ +#if HAVE_BIG_ENDIAN +#define HTON24(n) n +#else +#define HTON24(n) (((((uint32_t)(n) & 0xFF)) << 16)\ + | (((uint32_t)(n) & 0xFF00))\ + | ((((uint32_t)(n) & 0xFF0000)) >> 16)) +#endif + + +/** + * Convert 24 bit integer from network byte order to host byte order. + * + * @param n input value (int32_t) + * @return converted value (uint32_t) + */ +#if HAVE_BIG_ENDIAN +#define NTOH24(n) n +#else +#define NTOH24(n) (((((uint32_t)(n) & 0xFF)) << 16)\ + | (((uint32_t)(n) & 0xFF00))\ + | ((((uint32_t)(n) & 0xFF0000)) >> 16)) +#endif + + +/** + * Convert 31 bit integer from network byte order to host byte order. + * + * @param n input value (int32_t) + * @return converted value (uint32_t) + */ +#if HAVE_BIG_ENDIAN +#define NTOH31(n) n +#else +#define NTOH31(n) (((((uint32_t)(n) & 0x7F)) << 24) | \ + ((((uint32_t)(n) & 0xFF00)) << 8) | \ + ((((uint32_t)(n) & 0xFF0000)) >> 8) | \ + ((((uint32_t)(n) & 0xFF000000)) >> 24)) +#endif + + +/** + * Convert 31 bit integer from host byte order to network byte order. + * + * @param n input value (int32_t) + * @return converted value (uint32_t) + */ +#if HAVE_BIG_ENDIAN +#define HTON31(n) n +#else +#define HTON31(n) (((((uint32_t)(n) & 0xFF)) << 24) | \ + ((((uint32_t)(n) & 0xFF00)) << 8) | \ + ((((uint32_t)(n) & 0xFF0000)) >> 8) | \ + ((((uint32_t)(n) & 0x7F000000)) >> 24)) +#endif + + +/** + * Print formatted debug value. + * + * @param fmt format (const char *) + * @param ... args for format + */ +#define SPDYF_DEBUG(fmt, ...) do { \ + fprintf (stdout, "%s\n%u: ",__FILE__, __LINE__);\ + fprintf(stdout,fmt,##__VA_ARGS__);\ + fprintf(stdout,"\n");\ + fflush(stdout); } while (0) + + +/** + * Print stream for debuging. + * + * @param strm (void *) + * @param size (int) + */ +#define SPDYF_PRINT_STREAM(strm, size) do { \ + int ___i;\ + for(___i=0;___i<size;___i++){\ + fprintf(stdout,"%x ",*((uint8_t *) strm + ___i));\ + fflush(stdout);\ + }\ + fprintf(stdout,"\n");\ + } while (0) + + +/** + * Print message and raise SIGINT for debug purposes. + * + * @param msg message (const char *) + */ +#define SPDYF_SIGINT(msg) do { \ + fprintf(stdout,"%i : %s\n", __LINE__,__FILE__);\ + fprintf(stdout,msg);\ + fprintf(stdout,"\n");\ + fflush(stdout);\ + raise(SIGINT); } while (0) + + +/** + * Returns monotonic time, to be used for session timeouts. + * + * @return time in seconds + */ +time_t +SPDYF_monotonic_time(void); + +#endif diff --git a/src/microspdy/session.c b/src/microspdy/session.c @@ -0,0 +1,1554 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file session.c + * @brief TCP connection/SPDY session handling. So far most of the + * functions for handling SPDY framing layer are here. + * @author Andrey Uzunov + */ + +#include "platform.h" +#include "structures.h" +#include "internal.h" +#include "session.h" +#include "compression.h" +#include "tls.h" +#include "stream.h" + + +/** + * Handler for reading the full SYN_STREAM frame after we know that + * the frame is such. + * The function waits for the full frame and then changes status + * of the session. New stream is created. + * + * @param session SPDY_Session whose read buffer is used. + */ +static void +spdyf_handler_read_syn_stream (struct SPDY_Session *session) +{ + size_t name_value_strm_size = 0; + uint compressed_data_size; + int ret; + void *name_value_strm = NULL; + struct SPDYF_Control_Frame *frame; + struct SPDY_NameValue *headers; + + SPDYF_ASSERT(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status + || SPDY_SESSION_STATUS_WAIT_FOR_BODY == session->status, + "the function is called wrong"); + + frame = (struct SPDYF_Control_Frame *)session->frame_handler_cls; + + //handle subheaders + if(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status) + { + if(0 == frame->length) + { + //protocol error: incomplete frame + //we just ignore it since there is no stream id for which to + //send RST_STREAM + //TODO maybe GOAWAY and closing session is appropriate + SPDYF_DEBUG("zero long SYN_STREAM received"); + session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER; + free(frame); + return; + } + + if(SPDY_YES != SPDYF_stream_new(session)) + { + /* waiting for some more fields to create new stream + or something went wrong, SPDYF_stream_new has handled the + situation */ + return; + } + + session->current_stream_id = session->streams_head->stream_id; + if(frame->length > SPDY_MAX_SUPPORTED_FRAME_SIZE) + { + //TODO no need to create stream if this happens + session->status = SPDY_SESSION_STATUS_IGNORE_BYTES; + return; + } + else + session->status = SPDY_SESSION_STATUS_WAIT_FOR_BODY; + } + + //handle body + + //start reading the compressed name/value pairs (http headers) + compressed_data_size = frame->length //everything after length field + - 10;//4B stream id, 4B assoc strem id, 2B priority, unused and slot + + if(session->read_buffer_offset - session->read_buffer_beginning < compressed_data_size) + { + // the full frame is not yet here, try later + return; + } + + if(compressed_data_size > 0 + && SPDY_YES != SPDYF_zlib_inflate(&session->zlib_recv_stream, + session->read_buffer + session->read_buffer_beginning, + compressed_data_size, + &name_value_strm, + &name_value_strm_size)) + { + /* something went wrong on inflating, + * the state of the stream for decompression is unknown + * and we may not be able to read anything more received on + * this session, + * so it is better to close the session */ + free(name_value_strm); + free(frame); + + /* mark the session for closing and close it, when + * everything on the output queue is already written */ + session->status = SPDY_SESSION_STATUS_FLUSHING; + + SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_INTERNAL_ERROR, false); + + return; + } + + if(0 == name_value_strm_size || 0 == compressed_data_size) + { + //Protocol error: send RST_STREAM + if(SPDY_YES != SPDYF_prepare_rst_stream(session, session->current_stream_id, + SPDY_RST_STREAM_STATUS_PROTOCOL_ERROR)) + { + //no memory, try later to send RST + return; + } + } + + ret = SPDYF_name_value_from_stream(name_value_strm, name_value_strm_size, &headers); + if(SPDY_NO == ret) + { + //memory error, try later + free(name_value_strm); + return; + } + + session->streams_head->headers = headers; + //inform the application layer for the new stream received + if(SPDY_YES != session->daemon->fnew_stream_cb(session->daemon->fcls, session->streams_head)) + { + //memory error, try later + free(name_value_strm); + return; + } + + session->read_buffer_beginning += compressed_data_size; + //change state to wait for new frame + session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER; + free(frame); + free(name_value_strm); +} + + +/** + * Handler for reading the GOAWAY frame after we know that + * the frame is such. + * The function waits for the full frame and then changes status + * of the session. + * + * @param session SPDY_Session whose read buffer is used. + */ +static void +spdyf_handler_read_goaway (struct SPDY_Session *session) +{ + struct SPDYF_Control_Frame *frame; + uint32_t last_good_stream_id; + uint32_t status_int; + enum SPDY_GOAWAY_STATUS status; + + SPDYF_ASSERT(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status, + "the function is called wrong"); + + frame = (struct SPDYF_Control_Frame *)session->frame_handler_cls; + + if(frame->length > SPDY_MAX_SUPPORTED_FRAME_SIZE) + { + //this is a protocol error/attack + session->status = SPDY_SESSION_STATUS_IGNORE_BYTES; + return; + } + + if(0 != frame->flags || 8 != frame->length) + { + //this is a protocol error + SPDYF_DEBUG("wrong GOAWAY received"); + //anyway, it will be handled + } + + if((session->read_buffer_offset - session->read_buffer_beginning) < frame->length) + { + //not all fields are received + //try later + return; + } + + //mark that the session is almost closed + session->is_goaway_received = true; + + if(8 == frame->length) + { + memcpy(&last_good_stream_id, session->read_buffer + session->read_buffer_beginning, 4); + last_good_stream_id = NTOH31(last_good_stream_id); + session->read_buffer_beginning += 4; + + memcpy(&status_int, session->read_buffer + session->read_buffer_beginning, 4); + status = ntohl(status_int); + session->read_buffer_beginning += 4; + + //TODO do something with last_good + + //SPDYF_DEBUG("Received GOAWAY; status=%i; lastgood=%i",status,last_good_stream_id); + + //do something according to the status + //TODO + switch(status) + { + case SPDY_GOAWAY_STATUS_OK: + break; + case SPDY_GOAWAY_STATUS_PROTOCOL_ERROR: + break; + case SPDY_GOAWAY_STATUS_INTERNAL_ERROR: + break; + } + } + + session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER; + free(frame); +} + + +/** + * Handler for reading RST_STREAM frames. After receiving the frame + * the stream moves into closed state and status + * of the session is changed. Frames, belonging to this stream, which + * are still at the output queue, will be ignored later. + * + * @param session SPDY_Session whose read buffer is used. + */ +static void +spdyf_handler_read_rst_stream (struct SPDY_Session *session) +{ + struct SPDYF_Control_Frame *frame; + uint32_t stream_id; + int32_t status_int; + enum SPDY_RST_STREAM_STATUS status; + struct SPDYF_Stream *stream; + + SPDYF_ASSERT(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status, + "the function is called wrong"); + + frame = (struct SPDYF_Control_Frame *)session->frame_handler_cls; + + if(0 != frame->flags || 8 != frame->length) + { + //this is a protocol error + SPDYF_DEBUG("wrong RST_STREAM received"); + //ignore as a large frame + session->status = SPDY_SESSION_STATUS_IGNORE_BYTES; + return; + } + + if((session->read_buffer_offset - session->read_buffer_beginning) < frame->length) + { + //not all fields are received + //try later + return; + } + + memcpy(&stream_id, session->read_buffer + session->read_buffer_beginning, 4); + stream_id = NTOH31(stream_id); + session->read_buffer_beginning += 4; + + memcpy(&status_int, session->read_buffer + session->read_buffer_beginning, 4); + status = ntohl(status_int); + session->read_buffer_beginning += 4; + + session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER; + free(frame); + + //mark the stream as closed + stream = session->streams_head; + while(NULL != stream) + { + if(stream_id == stream->stream_id) + { + stream->is_in_closed = true; + stream->is_out_closed = true; + break; + } + stream = stream->next; + } + + SPDYF_DEBUG("Received RST_STREAM; status=%i; id=%i",status,stream_id); + + //do something according to the status + //TODO + /*switch(status) + { + case SPDY_RST_STREAM_STATUS_PROTOCOL_ERROR: + break; + }*/ +} + + +/** + * Handler for reading DATA frames. In requests they are used for POST + * arguments. + * + * @param session SPDY_Session whose read buffer is used. + */ +static void +spdyf_handler_read_data (struct SPDY_Session *session) +{ + (void)session; + //TODO ignore data frames for now + SPDYF_PANIC("POST requests are Not yet implemented!"); +} + + +int +SPDYF_handler_write_syn_reply (struct SPDY_Session *session) +{ + struct SPDYF_Response_Queue *response_queue = session->response_queue_head; + struct SPDYF_Stream *stream = response_queue->stream; + struct SPDYF_Control_Frame control_frame; + void *compressed_headers = NULL; + size_t compressed_headers_size=0; + size_t used_data=0; + size_t total_size; + uint32_t stream_id_nbo; + + SPDYF_ASSERT(NULL == session->write_buffer, "the function is called not in the correct moment"); + + memcpy(&control_frame, response_queue->control_frame, sizeof(control_frame)); + + if(SPDY_YES != SPDYF_zlib_deflate(&session->zlib_send_stream, + response_queue->data, + response_queue->data_size, + &used_data, + &compressed_headers, + &compressed_headers_size)) + { + /* something went wrong on compressing, + * the state of the stream for compression is unknown + * and we may not be able to send anything more on + * this session, + * so it is better to close the session right now */ + session->status = SPDY_SESSION_STATUS_CLOSING; + + free(compressed_headers); + + return SPDY_NO; + } + + //TODO do we need this used_Data + SPDYF_ASSERT(used_data == response_queue->data_size, "not everything was used by zlib"); + + total_size = sizeof(struct SPDYF_Control_Frame) //SPDY header + + 4 // stream id as "subheader" + + compressed_headers_size; + + if(NULL == (session->write_buffer = malloc(total_size))) + { + /* no memory + * since we do not save the compressed data anywhere and + * the sending zlib stream is already in new state, we must + * close the session */ + session->status = SPDY_SESSION_STATUS_CLOSING; + + free(compressed_headers); + + return SPDY_NO; + } + session->write_buffer_beginning = 0; + session->write_buffer_offset = 0; + session->write_buffer_size = total_size; + + control_frame.length = compressed_headers_size + 4; // compressed data + stream_id + SPDYF_CONTROL_FRAME_HTON(&control_frame); + + //put frame headers to write buffer + memcpy(session->write_buffer + session->write_buffer_offset,&control_frame,sizeof(struct SPDYF_Control_Frame)); + session->write_buffer_offset += sizeof(struct SPDYF_Control_Frame); + + //put stream id to write buffer + stream_id_nbo = HTON31(stream->stream_id); + memcpy(session->write_buffer + session->write_buffer_offset, &stream_id_nbo, 4); + session->write_buffer_offset += 4; + + //put compressed name/value pairs to write buffer + memcpy(session->write_buffer + session->write_buffer_offset, compressed_headers, compressed_headers_size); + session->write_buffer_offset += compressed_headers_size; + + SPDYF_ASSERT(0 == session->write_buffer_beginning, "bug1"); + SPDYF_ASSERT(session->write_buffer_offset == session->write_buffer_size, "bug2"); + + //DEBUG CODE, break compression state to see what happens +/* SPDYF_zlib_deflate(&session->zlib_send_stream, + "1234567890", + 10, + &used_data, + &compressed_headers, + &compressed_headers_size); +*/ + free(compressed_headers); + + session->last_replied_to_stream_id = stream->stream_id; + + return SPDY_YES; +} + + +int +SPDYF_handler_write_goaway (struct SPDY_Session *session) +{ + struct SPDYF_Response_Queue *response_queue = session->response_queue_head; + struct SPDYF_Control_Frame control_frame; + size_t total_size; + int last_good_stream_id; + + SPDYF_ASSERT(NULL == session->write_buffer, "the function is called not in the correct moment"); + + memcpy(&control_frame, response_queue->control_frame, sizeof(control_frame)); + + session->is_goaway_sent = true; + + total_size = sizeof(struct SPDYF_Control_Frame) //SPDY header + + 4 // last good stream id as "subheader" + + 4; // status code as "subheader" + + if(NULL == (session->write_buffer = malloc(total_size))) + { + return SPDY_NO; + } + session->write_buffer_beginning = 0; + session->write_buffer_offset = 0; + session->write_buffer_size = total_size; + + control_frame.length = 8; // always for GOAWAY + SPDYF_CONTROL_FRAME_HTON(&control_frame); + + //put frame headers to write buffer + memcpy(session->write_buffer + session->write_buffer_offset,&control_frame,sizeof(struct SPDYF_Control_Frame)); + session->write_buffer_offset += sizeof(struct SPDYF_Control_Frame); + + //put last good stream id to write buffer + last_good_stream_id = HTON31(session->last_replied_to_stream_id); + memcpy(session->write_buffer + session->write_buffer_offset, &last_good_stream_id, 4); + session->write_buffer_offset += 4; + + //put "data" to write buffer. This is the status + memcpy(session->write_buffer + session->write_buffer_offset, response_queue->data, 4); + session->write_buffer_offset += 4; + //data is not freed by the destroy function so: + //free(response_queue->data); + + SPDYF_ASSERT(0 == session->write_buffer_beginning, "bug1"); + SPDYF_ASSERT(session->write_buffer_offset == session->write_buffer_size, "bug2"); + + return SPDY_YES; +} + + +int +SPDYF_handler_write_data (struct SPDY_Session *session) +{ + struct SPDYF_Response_Queue *response_queue = session->response_queue_head; + struct SPDYF_Response_Queue *new_response_queue; + size_t total_size; + struct SPDYF_Data_Frame data_frame; + ssize_t ret; + bool more; + + SPDYF_ASSERT(NULL == session->write_buffer, "the function is called not in the correct moment"); + + memcpy(&data_frame, response_queue->data_frame, sizeof(data_frame)); + + if(NULL == response_queue->response->rcb) + { + //standard response with data into the struct + SPDYF_ASSERT(NULL != response_queue->data, "no data for the response"); + + total_size = sizeof(struct SPDYF_Data_Frame) //SPDY header + + response_queue->data_size; + + if(NULL == (session->write_buffer = malloc(total_size))) + { + return SPDY_NO; + } + session->write_buffer_beginning = 0; + session->write_buffer_offset = 0; + session->write_buffer_size = total_size; + + data_frame.length = response_queue->data_size; + SPDYF_DATA_FRAME_HTON(&data_frame); + + //put SPDY headers to the writing buffer + memcpy(session->write_buffer + session->write_buffer_offset,&data_frame,sizeof(struct SPDYF_Data_Frame)); + session->write_buffer_offset += sizeof(struct SPDYF_Data_Frame); + + //put data to the writing buffer + memcpy(session->write_buffer + session->write_buffer_offset, response_queue->data, response_queue->data_size); + session->write_buffer_offset += response_queue->data_size; + } + else + { + /* response with callbacks. The lib will produce more than 1 + * data frames + */ + + total_size = sizeof(struct SPDYF_Data_Frame) //SPDY header + + SPDY_MAX_SUPPORTED_FRAME_SIZE; //max possible size + + if(NULL == (session->write_buffer = malloc(total_size))) + { + return SPDY_NO; + } + session->write_buffer_beginning = 0; + session->write_buffer_offset = 0; + session->write_buffer_size = total_size; + + ret = response_queue->response->rcb(response_queue->response->rcb_cls, + session->write_buffer + sizeof(struct SPDYF_Data_Frame), + response_queue->response->rcb_block_size, + &more); + + if(ret < 0 || ret > response_queue->response->rcb_block_size) + { + //TODO send RST_STREAM (app error) + //for now close session + session->status = SPDY_SESSION_STATUS_CLOSING; + + free(session->write_buffer); + return SPDY_NO; + } + if(0 == ret && more) + { + //the app couldn't write anything to buf but later will + free(session->write_buffer); + session->write_buffer = NULL; + session->write_buffer_size = 0; + + if(NULL != response_queue->next) + { + //put the frame at the end of the queue + //otherwise - head of line blocking + session->response_queue_head = response_queue->next; + session->response_queue_head->prev = NULL; + session->response_queue_tail->next = response_queue; + response_queue->prev = session->response_queue_tail; + response_queue->next = NULL; + session->response_queue_tail = response_queue; + } + + return SPDY_YES; + } + + if(more) + { + //create another response queue object to call the user cb again + if(NULL == (new_response_queue = SPDYF_response_queue_create(true, + NULL, + 0, + response_queue->response, + response_queue->stream, + false, + response_queue->frqcb, + response_queue->frqcb_cls, + response_queue->rrcb, + response_queue->rrcb_cls))) + { + //TODO send RST_STREAM + //for now close session + session->status = SPDY_SESSION_STATUS_CLOSING; + + free(session->write_buffer); + return SPDY_NO; + } + + //put it at second position on the queue + new_response_queue->prev = response_queue; + new_response_queue->next = response_queue->next; + if(NULL == response_queue->next) + { + session->response_queue_tail = new_response_queue; + } + else + { + response_queue->next->prev = new_response_queue; + } + response_queue->next = new_response_queue; + + response_queue->frqcb = NULL; + response_queue->frqcb_cls = NULL; + response_queue->rrcb = NULL; + response_queue->rrcb_cls = NULL; + } + else + { + data_frame.flags |= SPDY_DATA_FLAG_FIN; + } + + data_frame.length = ret; + SPDYF_DATA_FRAME_HTON(&data_frame); + + //put SPDY headers to the writing buffer + memcpy(session->write_buffer + session->write_buffer_offset, + &data_frame, + sizeof(struct SPDYF_Data_Frame)); + session->write_buffer_offset += sizeof(struct SPDYF_Data_Frame); + session->write_buffer_offset += ret; + session->write_buffer_size = session->write_buffer_offset; + } + + SPDYF_ASSERT(0 == session->write_buffer_beginning, "bug1"); + SPDYF_ASSERT(session->write_buffer_offset == session->write_buffer_size, "bug2"); + + return SPDY_YES; +} + + +int +SPDYF_handler_write_rst_stream (struct SPDY_Session *session) +{ + struct SPDYF_Response_Queue *response_queue = session->response_queue_head; + struct SPDYF_Control_Frame control_frame; + size_t total_size; + + SPDYF_ASSERT(NULL == session->write_buffer, "the function is called not in the correct moment"); + + memcpy(&control_frame, response_queue->control_frame, sizeof(control_frame)); + + total_size = sizeof(struct SPDYF_Control_Frame) //SPDY header + + 4 // stream id as "subheader" + + 4; // status code as "subheader" + + if(NULL == (session->write_buffer = malloc(total_size))) + { + return SPDY_NO; + } + session->write_buffer_beginning = 0; + session->write_buffer_offset = 0; + session->write_buffer_size = total_size; + + control_frame.length = 8; // always for RST_STREAM + SPDYF_CONTROL_FRAME_HTON(&control_frame); + + //put frame headers to write buffer + memcpy(session->write_buffer + session->write_buffer_offset,&control_frame,sizeof(struct SPDYF_Control_Frame)); + session->write_buffer_offset += sizeof(struct SPDYF_Control_Frame); + + //put stream id to write buffer. This is the status + memcpy(session->write_buffer + session->write_buffer_offset, response_queue->data, 8); + session->write_buffer_offset += 8; + //data is not freed by the destroy function so: + //free(response_queue->data); + + SPDYF_ASSERT(0 == session->write_buffer_beginning, "bug1"); + SPDYF_ASSERT(session->write_buffer_offset == session->write_buffer_size, "bug2"); + + return SPDY_YES; +} + + +void +SPDYF_handler_ignore_frame (struct SPDY_Session *session) +{ + struct SPDYF_Control_Frame *frame; + + SPDYF_ASSERT(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status + || SPDY_SESSION_STATUS_WAIT_FOR_BODY == session->status, + "the function is called wrong"); + + + frame = (struct SPDYF_Control_Frame *)session->frame_handler_cls; + + //handle subheaders + if(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status) + { + if(frame->length > SPDY_MAX_SUPPORTED_FRAME_SIZE) + { + session->status = SPDY_SESSION_STATUS_IGNORE_BYTES; + return; + } + else + session->status = SPDY_SESSION_STATUS_WAIT_FOR_BODY; + } + + //handle body + + if(session->read_buffer_offset - session->read_buffer_beginning + >= frame->length) + { + session->read_buffer_beginning += frame->length; + session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER; + free(frame); + } +} + + +int +SPDYF_session_read (struct SPDY_Session *session) +{ + int bytes_read; + bool reallocate; + size_t actual_buf_size; + + if(SPDY_SESSION_STATUS_CLOSING == session->status + || SPDY_SESSION_STATUS_FLUSHING == session->status) + return SPDY_NO; + + //if the read buffer is full to the end, we need to reallocate space + if (session->read_buffer_size == session->read_buffer_offset) + { + //but only if the state of the session requires it + //i.e. no further proceeding is possible without reallocation + reallocate = false; + actual_buf_size = session->read_buffer_offset + - session->read_buffer_beginning; + switch(session->status) + { + case SPDY_SESSION_STATUS_WAIT_FOR_HEADER: + + case SPDY_SESSION_STATUS_IGNORE_BYTES: + //we need space for a whole control frame header + if(actual_buf_size < sizeof(struct SPDYF_Control_Frame)) + reallocate = true; + break; + + case SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER: + + case SPDY_SESSION_STATUS_WAIT_FOR_BODY: + //we need as many bytes as set in length field of the + //header + SPDYF_ASSERT(NULL != session->frame_handler_cls, + "no frame for session"); + if(session->frame_handler != &spdyf_handler_read_data) + { + if(actual_buf_size + < ((struct SPDYF_Control_Frame *)session->frame_handler_cls)->length) + reallocate = true; + } + else + { + if(actual_buf_size + < ((struct SPDYF_Data_Frame *)session->frame_handler_cls)->length) + reallocate = true; + } + break; + + case SPDY_SESSION_STATUS_CLOSING: + case SPDY_SESSION_STATUS_FLUSHING: + //nothing needed + break; + } + + if(reallocate) + { + //reuse the space in the buffer that was already read by the lib + memmove(session->read_buffer, + session->read_buffer + session->read_buffer_beginning, + session->read_buffer_offset - session->read_buffer_beginning); + + session->read_buffer_offset -= session->read_buffer_beginning; + session->read_buffer_beginning = 0; + } + else + { + //will read next time + //TODO optimize it, memmove more often? + return SPDY_NO; + } + } + + session->last_activity = SPDYF_monotonic_time(); + + //actual read from the TLS socket + bytes_read = SPDYF_tls_recv(session, + session->read_buffer + session->read_buffer_offset, + session->read_buffer_size - session->read_buffer_offset); + + switch(bytes_read) + { + case SPDY_TLS_ERROR_CLOSED: + //The TLS connection was closed by the other party, clean + //or not + shutdown (session->socket_fd, SHUT_RD); + session->read_closed = true; + session->status = SPDY_SESSION_STATUS_CLOSING; + return SPDY_YES; + + case SPDY_TLS_ERROR_ERROR: + //any kind of error in the TLS subsystem + //try to prepare GOAWAY frame + SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_INTERNAL_ERROR, false); + //try to flush the queue when write is called + session->status = SPDY_SESSION_STATUS_FLUSHING; + return SPDY_YES; + + case SPDY_TLS_ERROR_AGAIN: + //read or write should be called again; leave it for the + //next time + return SPDY_NO; + + //default: + //something was really read from the TLS subsystem + //just continue + } + + session->read_buffer_offset += bytes_read; + + return SPDY_YES; +} + + +int +SPDYF_session_write (struct SPDY_Session *session, bool only_one_frame) +{ + int i; + int bytes_written; + struct SPDYF_Response_Queue *queue_head; + struct SPDYF_Response_Queue *response_queue; + + if(SPDY_SESSION_STATUS_CLOSING == session->status) + return SPDY_NO; + + for(i=0; + only_one_frame + ? i < 1 + : i < SPDYF_NUM_SENT_FRAMES_AT_ONCE; + ++i) + { + //if the buffer is not null, part of the last frame is still + //pending to be sent + if(NULL == session->write_buffer) + { + //discard frames on closed streams + response_queue = session->response_queue_head; + + while(NULL != response_queue) + { + //if stream is closed, remove not yet sent frames + //associated with it + //GOAWAY frames are not associated to streams + //and still need to be sent + if(NULL == response_queue->stream + || !response_queue->stream->is_out_closed) + break; + + DLL_remove(session->response_queue_head,session->response_queue_tail,response_queue); + + if(NULL != response_queue->frqcb) + { + response_queue->frqcb(response_queue->frqcb_cls, response_queue, SPDY_RESPONSE_RESULT_STREAM_CLOSED); + } + + SPDYF_response_queue_destroy(response_queue); + response_queue = session->response_queue_head; + } + + if(NULL == session->response_queue_head) + break;//nothing on the queue + + //get next data from queue and put it to the write buffer + // to send it + if(SPDY_NO == session->response_queue_head->process_response_handler(session)) + { + //error occured and the handler changed or not the + //session's status appropriately + if(SPDY_SESSION_STATUS_CLOSING == session->status) + { + //try to send GOAWAY first if the current frame is different + if(session->response_queue_head->is_data + || SPDY_CONTROL_FRAME_TYPES_GOAWAY + != session->response_queue_head->control_frame->type) + { + session->status = SPDY_SESSION_STATUS_FLUSHING; + SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_INTERNAL_ERROR, true); + SPDYF_session_write(session,true); + session->status = SPDY_SESSION_STATUS_CLOSING; + } + return SPDY_YES; + } + + //just return from the loop to return from this function + break; + } + + //check if something was prepared for writing + //on respones with callbacks it is possible that their is no + //data available + if(0 == session->write_buffer_size)//nothing to write + if(response_queue != session->response_queue_head) + { + //the handler modified the queue + continue; + } + else + { + //no need to try the same frame again + break; + } + } + + session->last_activity = SPDYF_monotonic_time(); + + //actual write to the TLS socket + bytes_written = SPDYF_tls_send(session, + session->write_buffer + session->write_buffer_beginning, + session->write_buffer_offset - session->write_buffer_beginning); + + switch(bytes_written) + { + case SPDY_TLS_ERROR_CLOSED: + //The TLS connection was closed by the other party, clean + //or not + shutdown (session->socket_fd, SHUT_RD); + session->read_closed = true; + session->status = SPDY_SESSION_STATUS_CLOSING; + return SPDY_YES; + + case SPDY_TLS_ERROR_ERROR: + //any kind of error in the TLS subsystem + //forbid more writing + session->status = SPDY_SESSION_STATUS_CLOSING; + return SPDY_YES; + + case SPDY_TLS_ERROR_AGAIN: + //read or write should be called again; leave it for the + //next time; return from the function as we do not now + //whether reading or writing is needed + return i>0 ? SPDY_YES : SPDY_NO; + + //default: + //something was really read from the TLS subsystem + //just continue + } + + session->write_buffer_beginning += bytes_written; + + //check if the full buffer was written + if(session->write_buffer_beginning == session->write_buffer_size) + { + //that response is handled, remove it from queue + free(session->write_buffer); + session->write_buffer = NULL; + session->write_buffer_size = 0; + queue_head = session->response_queue_head; + if(NULL == queue_head->next) + { + session->response_queue_head = NULL; + session->response_queue_tail = NULL; + } + else + { + session->response_queue_head = queue_head->next; + session->response_queue_head->prev = NULL; + } + + //set stream to closed if the frame's fin flag is set + SPDYF_stream_set_flags(queue_head); + + if(NULL != queue_head->frqcb) + { + //application layer callback to notify sending of the response + queue_head->frqcb(queue_head->frqcb_cls, queue_head, SPDY_RESPONSE_RESULT_SUCCESS); + } + + SPDYF_response_queue_destroy(queue_head); + } + } + + if(SPDY_SESSION_STATUS_FLUSHING == session->status + && NULL == session->response_queue_head) + session->status = SPDY_SESSION_STATUS_CLOSING; + + return i>0 ? SPDY_YES : SPDY_NO; +} + + +int +SPDYF_session_idle (struct SPDY_Session *session) +{ + size_t read_buffer_beginning; + size_t frame_length; + struct SPDYF_Control_Frame* control_frame; + struct SPDYF_Data_Frame *data_frame; + + //prepare session for closing if timeout is used and already passed + if(SPDY_SESSION_STATUS_CLOSING != session->status + && session->daemon->session_timeout + && (session->last_activity + session->daemon->session_timeout < SPDYF_monotonic_time())) + { + session->status = SPDY_SESSION_STATUS_CLOSING; + //best effort for sending GOAWAY + SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_OK, true); + SPDYF_session_write(session,true); + } + + switch(session->status) + { + //expect new frame to arrive + case SPDY_SESSION_STATUS_WAIT_FOR_HEADER: + session->current_stream_id = 0; + //check if the whole frame header is already here + //both frame types have the same length + if(session->read_buffer_offset - session->read_buffer_beginning + < sizeof(struct SPDYF_Control_Frame)) + return SPDY_NO; + + /* check the first bit to see if it is data or control frame + * and also if the version is supported */ + if(0x80 == *(uint8_t *)(session->read_buffer + session->read_buffer_beginning) + && SPDY_VERSION == *((uint8_t *)session->read_buffer + session->read_buffer_beginning + 1)) + { + //control frame + if(NULL == (control_frame = malloc(sizeof(struct SPDYF_Control_Frame)))) + { + SPDYF_DEBUG("No memory"); + return SPDY_NO; + } + + //get frame headers + memcpy(control_frame, + session->read_buffer + session->read_buffer_beginning, + sizeof(struct SPDYF_Control_Frame)); + session->read_buffer_beginning += sizeof(struct SPDYF_Control_Frame); + SPDYF_CONTROL_FRAME_NTOH(control_frame); + + session->status = SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER; + //assign different frame handler according to frame type + switch(control_frame->type){ + case SPDY_CONTROL_FRAME_TYPES_SYN_STREAM: + session->frame_handler = &spdyf_handler_read_syn_stream; + break; + case SPDY_CONTROL_FRAME_TYPES_GOAWAY: + session->frame_handler = &spdyf_handler_read_goaway; + break; + case SPDY_CONTROL_FRAME_TYPES_RST_STREAM: + session->frame_handler = &spdyf_handler_read_rst_stream; + break; + default: + session->frame_handler = &SPDYF_handler_ignore_frame; + } + session->frame_handler_cls = control_frame; + //DO NOT break the outer case + } + else if(0 == *(uint8_t *)(session->read_buffer + session->read_buffer_beginning)) + { + //needed for POST + //data frame + if(NULL == (data_frame = malloc(sizeof(struct SPDYF_Data_Frame)))) + { + SPDYF_DEBUG("No memory"); + return SPDY_NO; + } + + //get frame headers + memcpy(data_frame, + session->read_buffer + session->read_buffer_beginning, + sizeof(struct SPDYF_Data_Frame)); + session->read_buffer_beginning += sizeof(struct SPDYF_Data_Frame); + + session->status = SPDY_SESSION_STATUS_WAIT_FOR_BODY; + session->frame_handler = &spdyf_handler_read_data; + session->frame_handler_cls = data_frame; + //DO NOT brake the outer case + } + else + { + SPDYF_DEBUG("another protocol or version received!"); + + /* According to the draft the lib should send here + * RST_STREAM with status UNSUPPORTED_VERSION. I don't + * see any sense of keeping the session open since + * we don't know how many bytes is the bogus "frame". + * And the latter normally will be HTTP request. + * + */ + + //shutdown(session->socket_fd, SHUT_RD); + session->status = SPDY_SESSION_STATUS_FLUSHING; + SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_PROTOCOL_ERROR,false); + //SPDYF_session_write(session,false); + /* close connection since the client expects another + protocol from us */ + //SPDYF_session_close(session); + return SPDY_YES; + } + + //expect specific header fields after the standard header + case SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER: + if(NULL!=session->frame_handler) + { + read_buffer_beginning = session->read_buffer_beginning; + //if everything is ok, the "body" will also be processed + //by the handler + session->frame_handler(session); + + if(SPDY_SESSION_STATUS_IGNORE_BYTES == session->status) + { + //check for larger than max supported frame + if(session->frame_handler != &spdyf_handler_read_data) + { + frame_length = ((struct SPDYF_Control_Frame *)session->frame_handler_cls)->length; + } + else + { + frame_length = ((struct SPDYF_Data_Frame *)session->frame_handler_cls)->length; + } + + //if(SPDY_MAX_SUPPORTED_FRAME_SIZE < frame_length) + { + SPDYF_DEBUG("received frame with unsupported size: %zu", frame_length); + //the data being received must be ignored and + //RST_STREAM sent + + //ignore bytes that will arive later + session->read_ignore_bytes = frame_length + + read_buffer_beginning + - session->read_buffer_offset; + //ignore what is already in read buffer + session->read_buffer_beginning = session->read_buffer_offset; + + SPDYF_prepare_rst_stream(session, + session->current_stream_id, //may be 0 here which is not good + SPDY_RST_STREAM_STATUS_FRAME_TOO_LARGE); + + //actually the read buffer can be bigger than the + //max supported size + session->status = session->read_ignore_bytes + ? SPDY_SESSION_STATUS_IGNORE_BYTES + : SPDY_SESSION_STATUS_WAIT_FOR_HEADER; + + free(session->frame_handler_cls); + } + } + } + + if(SPDY_SESSION_STATUS_IGNORE_BYTES != session->status) + { + break; + } + + //ignoring data in read buffer + case SPDY_SESSION_STATUS_IGNORE_BYTES: + SPDYF_ASSERT(session->read_ignore_bytes > 0, + "Session is in wrong state"); + if(session->read_ignore_bytes + > session->read_buffer_offset - session->read_buffer_beginning) + { + session->read_ignore_bytes -= + session->read_buffer_offset - session->read_buffer_beginning; + session->read_buffer_beginning = session->read_buffer_offset; + } + else + { + session->read_buffer_beginning += session->read_ignore_bytes; + session->read_ignore_bytes = 0; + session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER; + } + break; + + //expect frame body (name/value pairs) + case SPDY_SESSION_STATUS_WAIT_FOR_BODY: + if(NULL!=session->frame_handler) + session->frame_handler(session); + break; + + case SPDY_SESSION_STATUS_FLUSHING: + + return SPDY_NO; + + //because of error the session needs to be closed + case SPDY_SESSION_STATUS_CLOSING: + //error should be already sent to the client + SPDYF_session_close(session); + return SPDY_YES; + } + + return SPDY_YES; +} + + +void +SPDYF_session_close (struct SPDY_Session *session) +{ + struct SPDY_Daemon *daemon = session->daemon; + int by_client = session->read_closed ? SPDY_YES : SPDY_NO; + + //shutdown the tls and deinit the tls context + SPDYF_tls_close_session(session); + shutdown (session->socket_fd, + session->read_closed ? SHUT_WR : SHUT_RDWR); + session->read_closed = true; + + //remove session from the list + DLL_remove (daemon->sessions_head, + daemon->sessions_tail, + session); + //add the session for the list for cleaning up + DLL_insert (daemon->cleanup_head, + daemon->cleanup_tail, + session); + + //call callback for closed session + if(NULL != daemon->session_closed_cb) + { + daemon->session_closed_cb(daemon->cls, session, by_client); + } +} + + +int +SPDYF_session_accept(struct SPDY_Daemon *daemon) +{ + int new_socket_fd; + //int fd_flags; + struct SPDY_Session *session = NULL; + socklen_t addr_len; + struct sockaddr *addr; +#if HAVE_INET6 + struct sockaddr_in6 addr6; + + addr = (struct sockaddr *)&addr6; + addr_len = sizeof(addr6); +#else + struct sockaddr_in addr4; + + addr = (struct sockaddr *)&addr4; + addr_len = sizeof(addr6); +#endif + + new_socket_fd = accept (daemon->socket_fd, addr, &addr_len); + + if(new_socket_fd < 1) + return SPDY_NO; + + //setting the socket to be non-blocking + /* + * different handling is needed by libssl if non-blocking is used + * + fd_flags = fcntl (new_socket_fd, F_GETFL); + if ( -1 == fd_flags + || 0 != fcntl (new_socket_fd, F_SETFL, fd_flags | O_NONBLOCK)) + { + SPDYF_DEBUG("WARNING: Couldn't set the new connection to be non-blocking"); + } + */ + + if (NULL == (session = malloc (sizeof (struct SPDY_Session)))) + { + goto free_and_fail; + } + memset (session, 0, sizeof (struct SPDY_Session)); + + session->daemon = daemon; + session->socket_fd = new_socket_fd; + + //init TLS context, handshake will be done + if(SPDY_YES != SPDYF_tls_new_session(session)) + { + goto free_and_fail; + } + + //read buffer + session->read_buffer_size = SPDYF_BUFFER_SIZE; + if (NULL == (session->read_buffer = malloc (session->read_buffer_size))) + { + SPDYF_tls_close_session(session); + goto free_and_fail; + } + + //address of the client + if (NULL == (session->addr = malloc (addr_len))) + { + SPDYF_tls_close_session(session); + goto free_and_fail; + } + memcpy (session->addr, addr, addr_len); + + session->addr_len = addr_len; + session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER; + + //init zlib context for the whole session + if(SPDY_YES != SPDYF_zlib_deflate_init(&session->zlib_send_stream)) + { + SPDYF_tls_close_session(session); + goto free_and_fail; + } + if(SPDY_YES != SPDYF_zlib_inflate_init(&session->zlib_recv_stream)) + { + SPDYF_tls_close_session(session); + SPDYF_zlib_deflate_end(&session->zlib_send_stream); + goto free_and_fail; + } + + //add it to daemon's list + DLL_insert(daemon->sessions_head,daemon->sessions_tail,session); + + session->last_activity = SPDYF_monotonic_time(); + + if(NULL != daemon->new_session_cb) + daemon->new_session_cb(daemon->cls, session); + + return SPDY_YES; + + //for GOTO + free_and_fail: + /* something failed, so shutdown, close and free memory */ + shutdown (new_socket_fd, SHUT_RDWR); + close (new_socket_fd); + + if(NULL != session) + { + if(NULL != session->addr) + free (session->addr); + if(NULL != session->read_buffer) + free (session->read_buffer); + free (session); + } + return SPDY_NO; +} + + +void +SPDYF_queue_response (struct SPDYF_Response_Queue *response_to_queue, + struct SPDY_Session *session, + int consider_priority) +{ + struct SPDYF_Response_Queue *pos; + struct SPDYF_Response_Queue *last; + uint8_t priority; + + SPDYF_ASSERT(SPDY_YES != consider_priority || NULL != response_to_queue->stream, + "called with consider_priority but no stream provided"); + + last = response_to_queue; + while(NULL != last->next) + { + last = last->next; + } + + if(SPDY_NO == consider_priority) + { + //put it at the end of the queue + response_to_queue->prev = session->response_queue_tail; + if (NULL == session->response_queue_head) + session->response_queue_head = response_to_queue; + else + session->response_queue_tail->next = response_to_queue; + session->response_queue_tail = last; + return; + } + else if(-1 == consider_priority) + { + //put it at the head of the queue + last->next = session->response_queue_head; + if (NULL == session->response_queue_tail) + session->response_queue_tail = last; + else + session->response_queue_head->prev = response_to_queue; + session->response_queue_head = response_to_queue; + return; + } + + if(NULL == session->response_queue_tail) + { + session->response_queue_head = response_to_queue; + session->response_queue_tail = last; + return; + } + + //search for the right position to put it + pos = session->response_queue_tail; + priority = response_to_queue->stream->priority; + while(NULL != pos + && pos->stream->priority > priority) + { + pos = pos->prev; + } + + if(NULL == pos) + { + //put it on the head + session->response_queue_head->prev = last; + last->next = session->response_queue_head; + session->response_queue_head = response_to_queue; + } + else if(NULL == pos->next) + { + //put it at the end + response_to_queue->prev = pos; + pos->next = response_to_queue; + session->response_queue_tail = last; + } + else + { + response_to_queue->prev = pos; + last->next = pos->next; + pos->next = response_to_queue; + last->next->prev = last; + } +} + + +void +SPDYF_session_destroy(struct SPDY_Session *session) +{ + struct SPDYF_Stream *stream; + struct SPDYF_Response_Queue *response_queue; + + close (session->socket_fd); + SPDYF_zlib_deflate_end(&session->zlib_send_stream); + SPDYF_zlib_inflate_end(&session->zlib_recv_stream); + + //clean up unsent data in the output queue + while (NULL != (response_queue = session->response_queue_head)) + { + DLL_remove (session->response_queue_head, + session->response_queue_tail, + response_queue); + + if(NULL != response_queue->frqcb) + { + response_queue->frqcb(response_queue->frqcb_cls, response_queue, SPDY_RESPONSE_RESULT_SESSION_CLOSED); + } + + SPDYF_response_queue_destroy(response_queue); + } + + //clean up the streams belonging to this session + while (NULL != (stream = session->streams_head)) + { + DLL_remove (session->streams_head, + session->streams_tail, + stream); + + SPDYF_stream_destroy(stream); + } + + free(session->addr); + free(session->read_buffer); + free(session->write_buffer); + free(session); +} + + +int +SPDYF_prepare_goaway (struct SPDY_Session *session, + enum SPDY_GOAWAY_STATUS status, + bool in_front) +{ + struct SPDYF_Response_Queue *response_to_queue; + struct SPDYF_Control_Frame *control_frame; + uint32_t *data; + + if(NULL == (response_to_queue = malloc(sizeof(struct SPDYF_Response_Queue)))) + { + return SPDY_NO; + } + memset(response_to_queue, 0, sizeof(struct SPDYF_Response_Queue)); + + if(NULL == (control_frame = malloc(sizeof(struct SPDYF_Control_Frame)))) + { + free(response_to_queue); + return SPDY_NO; + } + memset(control_frame, 0, sizeof(struct SPDYF_Control_Frame)); + + if(NULL == (data = malloc(4))) + { + free(control_frame); + free(response_to_queue); + return SPDY_NO; + } + *(data) = htonl(status); + + control_frame->control_bit = 1; + control_frame->version = SPDY_VERSION; + control_frame->type = SPDY_CONTROL_FRAME_TYPES_GOAWAY; + control_frame->flags = 0; + + response_to_queue->control_frame = control_frame; + response_to_queue->process_response_handler = &SPDYF_handler_write_goaway; + response_to_queue->data = data; + response_to_queue->data_size = 4; + + SPDYF_queue_response (response_to_queue, + session, + in_front ? -1 : SPDY_NO); + + return SPDY_YES; +} + + +int +SPDYF_prepare_rst_stream (struct SPDY_Session *session, + uint32_t stream_id, + enum SPDY_RST_STREAM_STATUS status) +{ + struct SPDYF_Response_Queue *response_to_queue; + struct SPDYF_Control_Frame *control_frame; + uint32_t *data; + + if(NULL == (response_to_queue = malloc(sizeof(struct SPDYF_Response_Queue)))) + { + return SPDY_NO; + } + memset(response_to_queue, 0, sizeof(struct SPDYF_Response_Queue)); + + if(NULL == (control_frame = malloc(sizeof(struct SPDYF_Control_Frame)))) + { + free(response_to_queue); + return SPDY_NO; + } + memset(control_frame, 0, sizeof(struct SPDYF_Control_Frame)); + + if(NULL == (data = malloc(8))) + { + free(control_frame); + free(response_to_queue); + return SPDY_NO; + } + *(data) = HTON31(stream_id); + *(data + 1) = htonl(status); + + control_frame->control_bit = 1; + control_frame->version = SPDY_VERSION; + control_frame->type = SPDY_CONTROL_FRAME_TYPES_RST_STREAM; + control_frame->flags = 0; + + response_to_queue->control_frame = control_frame; + response_to_queue->process_response_handler = &SPDYF_handler_write_rst_stream; + response_to_queue->data = data; + response_to_queue->data_size = 8; + + SPDYF_queue_response (response_to_queue, + session, + -1); + + return SPDY_YES; +} diff --git a/src/microspdy/session.h b/src/microspdy/session.h @@ -0,0 +1,248 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file session.h + * @brief TCP connection/SPDY session handling + * @author Andrey Uzunov + */ + +#ifndef SESSION_H +#define SESSION_H + +#include "platform.h" +#include "structures.h" + +/** + * Called by the daemon when the socket for the session has available + * data to be read. Reads data from the TLS socket and puts it to the + * session's read buffer. The latte + * + * @param session SPDY_Session for which data will be read. + * @return SPDY_YES if something was read or session's status was + * changed. It is possible that error occurred but was handled + * and the status was therefore changed. + * SPDY_NO if nothing happened, e.g. the subsystem wants read/ + * write to be called again. + */ +int +SPDYF_session_read (struct SPDY_Session *session); + + +/** + * Called by the daemon when the socket for the session is ready for some + * data to be written to it. For one or more objects on the response + * queue tries to fill in the write buffer, based on the frame on the + * queue, and to write data to the TLS socket. + * + * @param session SPDY_Session for which data will be written. + * @return TODO document after changes + * SPDY_YES if something was written, the status was changed or + * response callback was called but did not provide data + * @return SPDY_YES if something was written, session's status was + * changed or response callback was called but did not provide + * data. It is possible that error occurred but was handled + * and the status was therefore changed. + * SPDY_NO if nothing happened, e.g. the subsystem wants read/ + * write to be called again. However, it is possible that some + * frames were discarded within the call, e.g. frames belonging + * to a closed stream. + */ +int +SPDYF_session_write (struct SPDY_Session *session, bool only_one_frame); + + +/** + * Called by the daemon on SPDY_run to handle the data in the read and write + * buffer of a session. Based on the state and the content of the read + * buffer new frames are received and interpreted, appropriate user + * callbacks are called and maybe something is put on the response queue + * ready to be handled by session_write. + * + * @param session SPDY_Session which will be handled. + * @return SPDY_YES if something from the read buffers was processed, + * session's status was changed and/or the session was closed. + * SPDY_NO if nothing happened, e.g. the session is in a state, + * not allowing processing read buffers. + */ +int +SPDYF_session_idle (struct SPDY_Session *session); + + +/** + * This function shutdowns the socket, moves the session structure to + * daemon's queue for sessions to be cleaned up. + * + * @param session SPDY_Session which will be handled. + */ +void +SPDYF_session_close (struct SPDY_Session *session); + + +/** + * Called to accept new TCP connection and create SPDY session. + * + * @param daemon SPDY_Daemon whose listening socket is used. + * @return SPDY_NO on any kind of error while accepting new TCP connection + * and initializing new SPDY_Session. + * SPDY_YES otherwise. + */ +int +SPDYF_session_accept(struct SPDY_Daemon *daemon); + + +/** + * Puts SPDYF_Response_Queue object on the queue to be sent to the + * client later. + * + * @param response_to_queue linked list of objects containing SPDY + * frame and data to be added to the queue + * @param session SPDY session for which the response is sent + * @param consider_priority if SPDY_NO, the list will be added to the + * end of the queue. + * If SPDY_YES, the response will be added after + * the last previously added response with priority of the + * request grater or equal to that of the current one. + * If -1, the object will be put at the head of the queue. + */ +void +SPDYF_queue_response (struct SPDYF_Response_Queue *response_to_queue, + struct SPDY_Session *session, + int consider_priority); + + +/** + * Cleans up the TSL context for the session, closes the TCP connection, + * cleans up any data pointed by members of the session structure + * (buffers, queue of responses, etc.) and frees the memory allocated by + * the session itself. + */ +void +SPDYF_session_destroy(struct SPDY_Session *session); + + +/** + * Prepares GOAWAY frame to tell the client to stop creating new streams. + * The session should be closed soon after this call. + * + * @param session SPDY session + * @param status code for the GOAWAY frame + * @param in_front whether or not to put the frame in front of everything + * on the response queue + * @return SPDY_NO on error (not enough memory) or + * SPDY_YES on success + */ +int +SPDYF_prepare_goaway (struct SPDY_Session *session, + enum SPDY_GOAWAY_STATUS status, + bool in_front); + + +/** + * Prepares RST_STREAM frame to terminate a stream. This frame may or + * not indicate an error. The frame will be put at the head of the queue. + * This means that frames for this stream which are still in the queue + * will be discarded soon. + * + * @param session SPDY session + * @param stream_id stream to terminate + * @param status code for the RST_STREAM frame + * @return SPDY_NO on memory error or + * SPDY_YES on success + */ +int +SPDYF_prepare_rst_stream (struct SPDY_Session *session, + uint32_t stream_id, + enum SPDY_RST_STREAM_STATUS status); + + +/** + * Handler called by session_write to fill the write buffer according to + * the data frame waiting in the response queue. + * When response data is given by user callback, the lib does not know + * how many frames are needed. In such case this call produces + * another ResponseQueue object and puts it on the queue while the the + * user callback says that there will be more data. + * + * @return SPDY_NO on error (not enough memory or the user calback for + * providing response data did something wrong). If + * the error is unrecoverable the handler changes session's + * status. + * SPDY_YES on success + */ +int +SPDYF_handler_write_data (struct SPDY_Session *session); + + +/** + * Handler called by session_write to fill the write buffer based on the + * control frame (SYN_REPLY) waiting in the response queue. + * + * @param session SPDY session + * @return SPDY_NO on error (zlib state is broken; the session MUST be + * closed). If + * the error is unrecoverable the handler changes session's + * status. + * SPDY_YES on success + */ +int +SPDYF_handler_write_syn_reply (struct SPDY_Session *session); + + +/** + * Handler called by session_write to fill the write buffer based on the + * control frame (GOAWAY) waiting in the response queue. + * + * @param session SPDY session + * @return SPDY_NO on error (not enough memory; by specification the + * session must be closed + * soon, thus there is no need to handle the error) or + * SPDY_YES on success + */ +int +SPDYF_handler_write_goaway (struct SPDY_Session *session); + + +/** + * Handler called by session_write to fill the write buffer based on the + * control frame (RST_STREAM) waiting in the response queue. + * + * @param session SPDY session + * @return SPDY_NO on error (not enough memory). If + * the error is unrecoverable the handler changes session's + * status. + * SPDY_YES on success + */ +int +SPDYF_handler_write_rst_stream (struct SPDY_Session *session); + + +/** + * Carefully ignore the full size of frames which are not yet supported + * by the lib. + * TODO Ignoring frames containing compressed bodies means that the + * compress state will be corrupted on next received frame. According to + * the draft the lib SHOULD try to decompress data also in corrupted + * frames just to keep right compression state. + * + * @param session SPDY_Session whose read buffer is used. + */ +void +SPDYF_handler_ignore_frame (struct SPDY_Session *session); + +#endif diff --git a/src/microspdy/stream.c b/src/microspdy/stream.c @@ -0,0 +1,151 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file stream.c + * @brief SPDY streams handling + * @author Andrey Uzunov + */ + +#include "platform.h" +#include "structures.h" +#include "internal.h" +#include "session.h" + + +int +SPDYF_stream_new (struct SPDY_Session *session) +{ + uint32_t stream_id; + uint32_t assoc_stream_id; + uint8_t priority; + uint8_t slot; + size_t buffer_pos = session->read_buffer_beginning; + struct SPDYF_Stream *stream; + struct SPDYF_Control_Frame *frame; + + if((session->read_buffer_offset - session->read_buffer_beginning) < 10) + { + //not all fields are received to create new stream + return SPDY_NO; + } + + frame = (struct SPDYF_Control_Frame *)session->frame_handler_cls; + + //get stream id of the new stream + memcpy(&stream_id, session->read_buffer + session->read_buffer_beginning, 4); + stream_id = NTOH31(stream_id); + session->read_buffer_beginning += 4; + if(stream_id <= session->last_in_stream_id || 0==(stream_id % 2)) + { + //wrong stream id sent by client + //GOAWAY with PROTOCOL_ERROR MUST be sent + //TODO + + //ignore frame + session->frame_handler = &SPDYF_handler_ignore_frame; + return SPDY_NO; + } + else if(session->is_goaway_sent) + { + //the client is not allowed to create new streams anymore + //we MUST ignore the frame + session->frame_handler = &SPDYF_handler_ignore_frame; + return SPDY_NO; + } + + //set highest stream id for session + session->last_in_stream_id = stream_id; + + //get assoc stream id of the new stream + //this value is used with SPDY PUSH, thus nothing to do with it here + memcpy(&assoc_stream_id, session->read_buffer + session->read_buffer_beginning, 4); + assoc_stream_id = NTOH31(assoc_stream_id); + session->read_buffer_beginning += 4; + + //get stream priority (3 bits) + //after it there are 5 bits that are not used + priority = *(uint8_t *)(session->read_buffer + session->read_buffer_beginning) >> 5; + session->read_buffer_beginning++; + + //get slot (see SPDY draft) + slot = *(uint8_t *)(session->read_buffer + session->read_buffer_beginning); + session->read_buffer_beginning++; + + if(NULL == (stream = malloc(sizeof(struct SPDYF_Stream)))) + { + SPDYF_DEBUG("No memory"); + //revert buffer state + session->read_buffer_beginning = buffer_pos; + return SPDY_NO; + } + memset(stream,0, sizeof(struct SPDYF_Stream)); + stream->session = session; + stream->stream_id = stream_id; + stream->assoc_stream_id = assoc_stream_id; + stream->priority = priority; + stream->slot = slot; + stream->is_in_closed = (frame->flags & SPDY_SYN_STREAM_FLAG_FIN) != 0; + stream->flag_unidirectional = (frame->flags & SPDY_SYN_STREAM_FLAG_UNIDIRECTIONAL) != 0; + stream->is_out_closed = stream->flag_unidirectional; + stream->is_server_initiator = false; + + //put the stream to the list of streams for the session + DLL_insert(session->streams_head, session->streams_tail, stream); + + return SPDY_YES; +} + + +void +SPDYF_stream_destroy(struct SPDYF_Stream *stream) +{ + SPDY_name_value_destroy(stream->headers); + free(stream); + stream = NULL; +} + + +void +SPDYF_stream_set_flags(struct SPDYF_Response_Queue *response_queue) +{ + struct SPDYF_Stream * stream = response_queue->stream; + + if(NULL != response_queue->data_frame) + { + stream->is_out_closed = (bool)(response_queue->data_frame->flags & SPDY_DATA_FLAG_FIN); + } + else if(NULL != response_queue->control_frame) + { + switch(response_queue->control_frame->type) + { + case SPDY_CONTROL_FRAME_TYPES_SYN_REPLY: + stream->is_out_closed = (bool)(response_queue->control_frame->flags & SPDY_SYN_REPLY_FLAG_FIN); + break; + + case SPDY_CONTROL_FRAME_TYPES_RST_STREAM: + if(NULL != stream) + { + stream->is_out_closed = true; + stream->is_in_closed = true; + } + break; + + } + } +} diff --git a/src/microspdy/stream.h b/src/microspdy/stream.h @@ -0,0 +1,65 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file stream.h + * @brief SPDY streams handling + * @author Andrey Uzunov + */ + +#ifndef STREAM_H +#define STREAM_H + +#include "platform.h" + + +/** + * Reads data from session's read buffer and tries to create a new SPDY + * stream. This function is called after control frame's header has been + * read from the buffer (after the length field). If bogus frame is + * received the function changes the read handler of the session and + * fails, i.e. there is no need of further error handling by the caller. + * + * @param session SPDY_Session whose read buffer is being read + * @return SPDY_YES if a new SPDY stream request was correctly received + * and handled. SPDY_NO if the whole SPDY frame was not yet + * received or memory error occurred. + */ +int +SPDYF_stream_new (struct SPDY_Session *session); + + +/** + * Destroys stream structure and whatever is in it. + * + * @param stream SPDY_Stream to destroy + */ +void +SPDYF_stream_destroy(struct SPDYF_Stream *stream); + + +/** + * Set stream flags if needed based on the type of the frame that was + * just sent (e.g., close stream if it was RST_STREAM). + * + * @param response_queue sent for this stream + */ +void +SPDYF_stream_set_flags(struct SPDYF_Response_Queue *response_queue); + +#endif diff --git a/src/microspdy/structures.c b/src/microspdy/structures.c @@ -0,0 +1,612 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file structures.c + * @brief Functions for handling most of the structures in defined + * in structures.h + * @author Andrey Uzunov + */ + +#include "platform.h" +#include "structures.h" +#include "internal.h" +#include "session.h" + + +struct SPDY_NameValue * +SPDY_name_value_create () +{ + struct SPDY_NameValue *pair; + + if(NULL == (pair = malloc(sizeof(struct SPDY_NameValue)))) + return NULL; + + memset (pair, 0, sizeof (struct SPDY_NameValue)); + + return pair; +} + + +int +SPDY_name_value_add (struct SPDY_NameValue *container, + const char *name, + const char *value) +{ + uint i; + uint len; + struct SPDY_NameValue *pair; + struct SPDY_NameValue *temp; + char **temp_value; + char *temp_string; + + if(NULL == container || NULL == name || 0 == (len = strlen(name))) + return SPDY_INPUT_ERROR; + + for(i=0; i<len; ++i) + { + if(isupper(name[i])) + return SPDY_INPUT_ERROR; + } + + if(NULL == container->name && NULL == container->value) + { + //container is empty/just created + if (NULL == (container->name = strdup (name))) + { + return SPDY_NO; + } + if (NULL == (container->value = malloc(sizeof(char *)))) + { + free(container->name); + return SPDY_NO; + } + if (NULL == (container->value[0] = strdup (value))) + { + free(container->value); + free(container->name); + return SPDY_NO; + } + container->num_values = 1; + return SPDY_YES; + } + + pair = container; + while(NULL != pair) + { + if(0 == strcmp(pair->name, name)) + { + //the value will be added to this pair + break; + } + pair = pair->next; + } + + if(NULL == pair) + { + //the name doesn't exist in container, add new pair + if(NULL == (pair = malloc(sizeof(struct SPDY_NameValue)))) + return SPDY_NO; + + memset(pair, 0, sizeof(struct SPDY_NameValue)); + + if (NULL == (pair->name = strdup (name))) + { + free(pair); + return SPDY_NO; + } + if (NULL == (pair->value = malloc(sizeof(char *)))) + { + free(pair->name); + free(pair); + return SPDY_NO; + } + if (NULL == (pair->value[0] = strdup (value))) + { + free(pair->value); + free(pair->name); + free(pair); + return SPDY_NO; + } + pair->num_values = 1; + + temp = container; + while(NULL != temp->next) + temp = temp->next; + temp->next = pair; + pair->prev = temp; + + return SPDY_YES; + } + + //check for duplication (case sensitive) + for(i=0; i<pair->num_values; ++i) + if(0 == strcmp(pair->value[i], value)) + return SPDY_NO; + + if(strlen(pair->value[0]) > 0) + { + //the value will be appended to the others for this name + if (NULL == (temp_value = malloc((pair->num_values + 1) * sizeof(char *)))) + { + return SPDY_NO; + } + memcpy(temp_value, pair->value, pair->num_values * sizeof(char *)); + if (NULL == (temp_value[pair->num_values] = strdup (value))) + { + free(temp_value); + return SPDY_NO; + } + free(pair->value); + pair->value = temp_value; + ++pair->num_values; + return SPDY_YES; + } + + //just replace the empty value + + if (NULL == (temp_string = strdup (value))) + { + return SPDY_NO; + } + free(pair->value[0]); + pair->value[0] = temp_string; + + return SPDY_YES; +} + + +const char * const * +SPDY_name_value_lookup (struct SPDY_NameValue *container, + const char *name, + int *num_values) +{ + struct SPDY_NameValue *temp = container; + + if(NULL == container || NULL == name || NULL == num_values) + return NULL; + if(NULL == container->name && NULL == container->value) + return NULL; + + do + { + if(strcmp(name, temp->name) == 0) + { + *num_values = temp->num_values; + return (const char * const *)temp->value; + } + + temp = temp->next; + } + while(NULL != temp); + + return NULL; +} + + +void +SPDY_name_value_destroy (struct SPDY_NameValue *container) +{ + uint i; + struct SPDY_NameValue *temp = container; + + while(NULL != temp) + { + container = container->next; + free(temp->name); + for(i=0; i<temp->num_values; ++i) + free(temp->value[i]); + free(temp->value); + free(temp); + temp=container; + } +} + + +int +SPDY_name_value_iterate (struct SPDY_NameValue *container, + SPDY_NameValueIterator iterator, + void *iterator_cls) +{ + int count; + int ret; + struct SPDY_NameValue *temp = container; + + if(NULL == container) + return SPDY_INPUT_ERROR; + + //check if container is an empty struct + if(NULL == container->name && NULL == container->value) + return 0; + + count = 0; + + if(NULL == iterator) + { + do + { + ++count; + temp=temp->next; + } + while(NULL != temp); + + return count; + } + + //code duplication for avoiding if here + do + { + ++count; + ret = iterator(iterator_cls, temp->name, (const char * const *)temp->value, temp->num_values); + temp=temp->next; + } + while(NULL != temp && SPDY_YES == ret); + + return count; +} + +void +SPDY_destroy_response(struct SPDY_Response *response) +{ + free(response->data); + free(response->headers); + free(response); +} + + +struct SPDYF_Response_Queue * +SPDYF_response_queue_create(bool is_data, + void *data, + size_t data_size, + struct SPDY_Response *response, + struct SPDYF_Stream *stream, + bool closestream, + SPDYF_ResponseQueueResultCallback frqcb, + void *frqcb_cls, + SPDY_ResponseResultCallback rrcb, + void *rrcb_cls) +{ + struct SPDYF_Response_Queue *head = NULL; + struct SPDYF_Response_Queue *prev; + struct SPDYF_Response_Queue *response_to_queue; + struct SPDYF_Control_Frame *control_frame; + struct SPDYF_Data_Frame *data_frame; + uint i; + bool is_last; + + SPDYF_ASSERT(!is_data + || 0 == data_size && NULL != response->rcb + || 0 < data_size && NULL == response->rcb, + "either data or request->rcb must not be null"); + + if(is_data && data_size > SPDY_MAX_SUPPORTED_FRAME_SIZE) + { + //separate the data in more frames and add them to the queue + + prev=NULL; + for(i = 0; i < data_size; i += SPDY_MAX_SUPPORTED_FRAME_SIZE) + { + is_last = (i + SPDY_MAX_SUPPORTED_FRAME_SIZE) >= data_size; + + if(NULL == (response_to_queue = malloc(sizeof(struct SPDYF_Response_Queue)))) + goto free_and_fail; + + memset(response_to_queue, 0, sizeof(struct SPDYF_Response_Queue)); + if(0 == i) + head = response_to_queue; + + if(NULL == (data_frame = malloc(sizeof(struct SPDYF_Data_Frame)))) + { + free(response_to_queue); + goto free_and_fail; + } + memset(data_frame, 0, sizeof(struct SPDYF_Data_Frame)); + data_frame->control_bit = 0; + data_frame->stream_id = stream->stream_id; + if(is_last && closestream) + data_frame->flags |= SPDY_DATA_FLAG_FIN; + + response_to_queue->data_frame = data_frame; + response_to_queue->process_response_handler = &SPDYF_handler_write_data; + response_to_queue->is_data = is_data; + response_to_queue->stream = stream; + if(is_last) + { + response_to_queue->frqcb = frqcb; + response_to_queue->frqcb_cls = frqcb_cls; + response_to_queue->rrcb = rrcb; + response_to_queue->rrcb_cls = rrcb_cls; + } + response_to_queue->data = data + i; + response_to_queue->data_size = is_last + ? (data_size - 1) % SPDY_MAX_SUPPORTED_FRAME_SIZE + 1 + : SPDY_MAX_SUPPORTED_FRAME_SIZE; + response_to_queue->response = response; + + response_to_queue->prev = prev; + if(NULL != prev) + prev->next = response_to_queue; + prev = response_to_queue; + } + + return head; + + //for GOTO + free_and_fail: + while(NULL != head) + { + response_to_queue = head; + head = head->next; + free(response_to_queue->data_frame); + free(response_to_queue); + } + return NULL; + } + + //create only one frame for data, data with callback or control frame + + if(NULL == (response_to_queue = malloc(sizeof(struct SPDYF_Response_Queue)))) + { + return NULL; + } + memset(response_to_queue, 0, sizeof(struct SPDYF_Response_Queue)); + + if(is_data) + { + if(NULL == (data_frame = malloc(sizeof(struct SPDYF_Data_Frame)))) + { + free(response_to_queue); + return NULL; + } + memset(data_frame, 0, sizeof(struct SPDYF_Data_Frame)); + data_frame->control_bit = 0; + data_frame->stream_id = stream->stream_id; + if(closestream && NULL == response->rcb) + data_frame->flags |= SPDY_DATA_FLAG_FIN; + + response_to_queue->data_frame = data_frame; + response_to_queue->process_response_handler = &SPDYF_handler_write_data; + } + else + { + if(NULL == (control_frame = malloc(sizeof(struct SPDYF_Control_Frame)))) + { + free(response_to_queue); + return NULL; + } + memset(control_frame, 0, sizeof(struct SPDYF_Control_Frame)); + control_frame->control_bit = 1; + control_frame->version = SPDY_VERSION; + control_frame->type = SPDY_CONTROL_FRAME_TYPES_SYN_REPLY; + if(closestream) + control_frame->flags |= SPDY_SYN_REPLY_FLAG_FIN; + + response_to_queue->control_frame = control_frame; + response_to_queue->process_response_handler = &SPDYF_handler_write_syn_reply; + } + + response_to_queue->is_data = is_data; + response_to_queue->stream = stream; + response_to_queue->frqcb = frqcb; + response_to_queue->frqcb_cls = frqcb_cls; + response_to_queue->rrcb = rrcb; + response_to_queue->rrcb_cls = rrcb_cls; + response_to_queue->data = data; + response_to_queue->data_size = data_size; + response_to_queue->response = response; + + return response_to_queue; +} + + +void +SPDYF_response_queue_destroy(struct SPDYF_Response_Queue *response_queue) +{ + //data is not copied to the struct but only linked + //but this is not valid for GOAWAY and RST_STREAM + if(!response_queue->is_data + && (SPDY_CONTROL_FRAME_TYPES_RST_STREAM == response_queue->control_frame->type + || SPDY_CONTROL_FRAME_TYPES_GOAWAY == response_queue->control_frame->type)) + { + free(response_queue->data); + } + if(response_queue->is_data) + free(response_queue->data_frame); + else + free(response_queue->control_frame); + + free(response_queue); +} + + +ssize_t +SPDYF_name_value_to_stream(struct SPDY_NameValue * container[], + int num_containers, + void **stream) +{ + size_t size; + int32_t num_pairs = 0; + int32_t value_size; + int32_t name_size; + int32_t temp; + uint i; + uint offset; + uint value_offset; + struct SPDY_NameValue * iterator; + int j; + + size = 4; //for num pairs + + for(j=0; j<num_containers; ++j) + { + iterator = container[j]; + while(iterator != NULL) + { + ++num_pairs; + size += 4 + strlen(iterator->name); //length + string + + SPDYF_ASSERT(iterator->num_values>0, "num_values is 0"); + + size += 4; //value length + + for(i=0; i<iterator->num_values; ++i) + { + size += strlen(iterator->value[i]); // string + if(i/* || !strlen(iterator->value[i])*/) ++size; //NULL separator + } + + iterator = iterator->next; + } +} + + if(NULL == (*stream = malloc(size))) + { + return -1; + } + + //put num_pairs to the stream + num_pairs = htonl(num_pairs); + memcpy(*stream, &num_pairs, 4); + offset = 4; + + //put all other headers to the stream + for(j=0; j<num_containers; ++j) + { + iterator = container[j]; + while(iterator != NULL) + { + name_size = strlen(iterator->name); + temp = htonl(name_size); + memcpy(*stream + offset, &temp, 4); + offset += 4; + strncpy(*stream + offset, iterator->name, name_size); + offset += name_size; + + value_offset = offset; + offset += 4; + for(i=0; i<iterator->num_values; ++i) + { + if(i /*|| !strlen(iterator->value[0])*/) + { + memset(*stream + offset, 0, 1); + ++offset; + if(!i) continue; + } + strncpy(*stream + offset, iterator->value[i], strlen(iterator->value[i])); + offset += strlen(iterator->value[i]); + } + value_size = offset - value_offset - 4; + value_size = htonl(value_size); + memcpy(*stream + value_offset, &value_size, 4); + + iterator = iterator->next; + } +} + + SPDYF_ASSERT(offset == size,"offset is wrong"); + + return size; +} + + +int +SPDYF_name_value_from_stream(void *stream, + size_t size, + struct SPDY_NameValue ** container) +{ + int32_t num_pairs; + int32_t value_size; + int32_t name_size; + int i; + uint offset = 0; + uint value_end_offset; + char *name; + char *value; + + if(NULL == (*container = SPDY_name_value_create ())) + { + return SPDY_NO; + } + + //get number of pairs + memcpy(&num_pairs, stream, 4); + offset = 4; + num_pairs = ntohl(num_pairs); + + if(num_pairs > 0) + { + for(i = 0; i < num_pairs; ++i) + { + //get name size + memcpy(&name_size, stream + offset, 4); + offset += 4; + name_size = ntohl(name_size); + //get name + if(NULL == (name = strndup(stream + offset, name_size))) + { + SPDY_name_value_destroy(*container); + return SPDY_NO; + } + offset+=name_size; + + //get value size + memcpy(&value_size, stream + offset, 4); + offset += 4; + value_size = ntohl(value_size); + value_end_offset = offset + value_size; + //get value + do + { + if(NULL == (value = strndup(stream + offset, value_size))) + { + free(name); + SPDY_name_value_destroy(*container); + return SPDY_NO; + } + offset += strlen(value); + if(offset < value_end_offset) + ++offset; //NULL separator + + //add name/value to the struct + if(SPDY_YES != SPDY_name_value_add(*container, name, value)) + { + free(name); + free(value); + SPDY_name_value_destroy(*container); + return SPDY_NO; + } + free(value); + } + while(offset < value_end_offset); + + free(name); + + if(offset != value_end_offset) + { + SPDY_name_value_destroy(*container); + return SPDY_INPUT_ERROR; + } + } + } + + if(offset == size) + return SPDY_YES; + + SPDY_name_value_destroy(*container); + return SPDY_INPUT_ERROR; +} diff --git a/src/microspdy/structures.h b/src/microspdy/structures.h @@ -0,0 +1,1128 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file structures.h + * @brief internal and public structures -- most of the structs used by + * the library are defined here + * @author Andrey Uzunov + */ + +#ifndef STRUCTURES_H +#define STRUCTURES_H + +#include "platform.h" +#include "microspdy.h" +#include "tls.h" + + +/** + * All possible SPDY control frame types. The number is used in the header + * of the control frame. + */ +enum SPDY_CONTROL_FRAME_TYPES +{ + /** + * The SYN_STREAM control frame allows the sender to asynchronously + * create a stream between the endpoints. + */ + SPDY_CONTROL_FRAME_TYPES_SYN_STREAM = 1, + + /** + * SYN_REPLY indicates the acceptance of a stream creation by + * the recipient of a SYN_STREAM frame. + */ + SPDY_CONTROL_FRAME_TYPES_SYN_REPLY = 2, + + /** + * The RST_STREAM frame allows for abnormal termination of a stream. + * When sent by the creator of a stream, it indicates the creator + * wishes to cancel the stream. When sent by the recipient of a + * stream, it indicates an error or that the recipient did not want + * to accept the stream, so the stream should be closed. + */ + SPDY_CONTROL_FRAME_TYPES_RST_STREAM = 3, + + /** + * A SETTINGS frame contains a set of id/value pairs for + * communicating configuration data about how the two endpoints may + * communicate. SETTINGS frames can be sent at any time by either + * endpoint, are optionally sent, and are fully asynchronous. When + * the server is the sender, the sender can request that + * configuration data be persisted by the client across SPDY + * sessions and returned to the server in future communications. + */ + SPDY_CONTROL_FRAME_TYPES_SETTINGS = 4, + + /** + * The PING control frame is a mechanism for measuring a minimal + * round-trip time from the sender. It can be sent from the client + * or the server. Recipients of a PING frame should send an + * identical frame to the sender as soon as possible (if there is + * other pending data waiting to be sent, PING should take highest + * priority). Each ping sent by a sender should use a unique ID. + */ + SPDY_CONTROL_FRAME_TYPES_PING = 6, + + /** + * The GOAWAY control frame is a mechanism to tell the remote side + * of the connection to stop creating streams on this session. It + * can be sent from the client or the server. + */ + SPDY_CONTROL_FRAME_TYPES_GOAWAY = 7, + + /** + * The HEADERS frame augments a stream with additional headers. It + * may be optionally sent on an existing stream at any time. + * Specific application of the headers in this frame is + * application-dependent. The name/value header block within this + * frame is compressed. + */ + SPDY_CONTROL_FRAME_TYPES_HEADERS = 8, + + /** + * The WINDOW_UPDATE control frame is used to implement per stream + * flow control in SPDY. Flow control in SPDY is per hop, that is, + * only between the two endpoints of a SPDY connection. If there are + * one or more intermediaries between the client and the origin + * server, flow control signals are not explicitly forwarded by the + * intermediaries. + */ + SPDY_CONTROL_FRAME_TYPES_WINDOW_UPDATE = 9, + + /** + * The CREDENTIAL control frame is used by the client to send + * additional client certificates to the server. A SPDY client may + * decide to send requests for resources from different origins on + * the same SPDY session if it decides that that server handles both + * origins. For example if the IP address associated with both + * hostnames matches and the SSL server certificate presented in the + * initial handshake is valid for both hostnames. However, because + * the SSL connection can contain at most one client certificate, + * the client needs a mechanism to send additional client + * certificates to the server. + */ + SPDY_CONTROL_FRAME_TYPES_CREDENTIAL = 11 +}; + + +/** + * SPDY_SESSION_STATUS is used to show the current receiving state + * of each session, i.e. what is expected to come now, and how it should + * be handled. + */ +enum SPDY_SESSION_STATUS +{ + /** + * The session is in closing state, do not read read anything from + * it. Do not write anything to it. + */ + SPDY_SESSION_STATUS_CLOSING = 0, + + /** + * Wait for new SPDY frame to come. + */ + SPDY_SESSION_STATUS_WAIT_FOR_HEADER = 1, + + /** + * The standard 8 byte header of the SPDY frame was received and + * handled. Wait for the specific (sub)headers according to the + * frame type. + */ + SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER = 2, + + /** + * The specific (sub)headers were received and handled. Wait for the + * "body", i.e. wait for the name/value pairs compressed by zlib. + */ + SPDY_SESSION_STATUS_WAIT_FOR_BODY = 3, + + /** + * Ignore all the bytes read from the socket, e.g. larger frames. + */ + SPDY_SESSION_STATUS_IGNORE_BYTES= 4, + + /** + * The session is in pre-closing state, do not read read anything + * from it. In this state the output queue will be written to the + * socket. + */ + SPDY_SESSION_STATUS_FLUSHING = 5, +}; + + +/** + * Specific flags for the SYN_STREAM control frame. + */ +enum SPDY_SYN_STREAM_FLAG +{ + /** + * The sender won't send any more frames on this stream. + */ + SPDY_SYN_STREAM_FLAG_FIN = 1, + + /** + * The sender creates this stream as unidirectional. + */ + SPDY_SYN_STREAM_FLAG_UNIDIRECTIONAL = 2 +}; + + +/** + * Specific flags for the SYN_REPLY control frame. + */ +enum SPDY_SYN_REPLY_FLAG +{ + /** + * The sender won't send any more frames on this stream. + */ + SPDY_SYN_REPLY_FLAG_FIN = 1 +}; + + +/** + * Specific flags for the data frame. + */ +enum SPDY_DATA_FLAG +{ + /** + * The sender won't send any more frames on this stream. + */ + SPDY_DATA_FLAG_FIN = 1, + + /** + * The data in the frame is compressed. + * This flag appears only in the draft on ietf.org but not on + * chromium.org. + */ + SPDY_DATA_FLAG_COMPRESS = 2 +}; + +/** + * Status code within RST_STREAM control frame. + */ +enum SPDY_RST_STREAM_STATUS +{ + /** + * This is a generic error, and should only be used if a more + * specific error is not available. + */ + SPDY_RST_STREAM_STATUS_PROTOCOL_ERROR = 1, + + /** + * This is returned when a frame is received for a stream which is + * not active. + */ + SPDY_RST_STREAM_STATUS_INVALID_STREAM = 2, + + /** + * Indicates that the stream was refused before any processing has + * been done on the stream. + */ + SPDY_RST_STREAM_STATUS_REFUSED_STREAM = 3, + + /** + * Indicates that the recipient of a stream does not support the + * SPDY version requested. + */ + SPDY_RST_STREAM_STATUS_UNSUPPORTED_VERSION = 4, + + /** + * Used by the creator of a stream to indicate that the stream is + * no longer needed. + */ + SPDY_RST_STREAM_STATUS_CANCEL = 5, + + /** + * This is a generic error which can be used when the implementation + * has internally failed, not due to anything in the protocol. + */ + SPDY_RST_STREAM_STATUS_INTERNAL_ERROR = 6, + + /** + * The endpoint detected that its peer violated the flow control + * protocol. + */ + SPDY_RST_STREAM_STATUS_FLOW_CONTROL_ERROR = 7, + + /** + * The endpoint received a SYN_REPLY for a stream already open. + */ + SPDY_RST_STREAM_STATUS_STREAM_IN_USE = 8, + + /** + * The endpoint received a data or SYN_REPLY frame for a stream + * which is half closed. + */ + SPDY_RST_STREAM_STATUS_STREAM_ALREADY_CLOSED = 9, + + /** + * The server received a request for a resource whose origin does + * not have valid credentials in the client certificate vector. + */ + SPDY_RST_STREAM_STATUS_INVALID_CREDENTIALS = 10, + + /** + * The endpoint received a frame which this implementation could not + * support. If FRAME_TOO_LARGE is sent for a SYN_STREAM, HEADERS, + * or SYN_REPLY frame without fully processing the compressed + * portion of those frames, then the compression state will be + * out-of-sync with the other endpoint. In this case, senders of + * FRAME_TOO_LARGE MUST close the session. + */ + SPDY_RST_STREAM_STATUS_FRAME_TOO_LARGE = 11 +}; + + +/** + * Status code within GOAWAY control frame. + */ +enum SPDY_GOAWAY_STATUS +{ + /** + * This is a normal session teardown. + */ + SPDY_GOAWAY_STATUS_OK = 0, + + /** + * This is a generic error, and should only be used if a more + * specific error is not available. + */ + SPDY_GOAWAY_STATUS_PROTOCOL_ERROR = 1, + + /** + * This is a generic error which can be used when the implementation + * has internally failed, not due to anything in the protocol. + */ + SPDY_GOAWAY_STATUS_INTERNAL_ERROR = 11 +}; + + +struct SPDYF_Stream; + +struct SPDYF_Response_Queue; + +/** + * Callback for new stream. To be used in the application layer of the + * lib. + * + * @param cls + * @param stream the new stream + * @return SPDY_YES on success, + * SPDY_NO if error occurs + */ +typedef int +(*SPDYF_NewStreamCallback) (void *cls, + struct SPDYF_Stream * stream); + + +/** + * Callback to be called when the response queue object was handled and + * the data was already sent. + * + * @param cls + * @param response_queue the SPDYF_Response_Queue structure which will + * be cleaned very soon + * @param status shows if actually the response was sent or it was + * discarded by the lib for any reason (e.g., closing session, + * closing stream, stopping daemon, etc.). It is possible that + * status indicates an error but part of the response (in one + * or several frames) was sent to the client. + */ +typedef void +(*SPDYF_ResponseQueueResultCallback) (void * cls, + struct SPDYF_Response_Queue *response_queue, + enum SPDY_RESPONSE_RESULT status); + + +/** + * Representation of the control frame's headers, which are common for + * all types. + */ +struct __attribute__((__packed__)) SPDYF_Control_Frame +{ + uint16_t version : 15; + uint16_t control_bit : 1; /* always 1 for control frames */ + uint16_t type; + uint32_t flags : 8; + uint32_t length : 24; +}; + + +/** + * Representation of the data frame's headers. + */ +struct __attribute__((__packed__)) SPDYF_Data_Frame +{ + uint32_t stream_id : 31; + uint32_t control_bit : 1; /* always 0 for data frames */ + uint32_t flags : 8; + uint32_t length : 24; +}; + + +/** + * Queue of the responses, to be handled (e.g. compressed) and sent later. + */ +struct SPDYF_Response_Queue +{ + /** + * This is a doubly-linked list. + */ + struct SPDYF_Response_Queue *next; + + /** + * This is a doubly-linked list. + */ + struct SPDYF_Response_Queue *prev; + + /** + * Stream (Request) for which is the response. + */ + struct SPDYF_Stream *stream; + + /** + * Response structure with all the data (uncompressed headers) to be sent. + */ + struct SPDY_Response *response; + + /** + * Control frame. The length field should be set after compressing + * the headers! + */ + struct SPDYF_Control_Frame *control_frame; + + /** + * Data frame. The length field should be set after compressing + * the body! + */ + struct SPDYF_Data_Frame *data_frame; + + /** + * Data to be sent: name/value pairs in control frames or body in data frames. + */ + void *data; + + /** + * Specific handler for different frame types. + */ + int (* process_response_handler)(struct SPDY_Session *session); + + /** + * Callback to be called when the last bytes from the response was sent + * to the client. + */ + SPDYF_ResponseQueueResultCallback frqcb; + + /** + * Closure for frqcb. + */ + void *frqcb_cls; + + /** + * Callback to be used by the application layer. + */ + SPDY_ResponseResultCallback rrcb; + + /** + * Closure for rcb. + */ + void *rrcb_cls; + + /** + * Data size. + */ + size_t data_size; + + /** + * True if data frame should be sent. False if control frame should + * be sent. + */ + bool is_data; +}; + + + +/** + * Collection of HTTP headers used in requests and responses. + */ +struct SPDY_NameValue +{ + /** + * This is a doubly-linked list. + */ + struct SPDY_NameValue *next; + + /** + * This is a doubly-linked list. + */ + struct SPDY_NameValue *prev; + + /** + * Null terminated string for name. + */ + char *name; + + /** + * Array of Null terminated strings for value. num_values is the + * length of the array. + */ + char **value; + + /** + * Number of values, this is >= 0. + */ + uint num_values; +}; + + +/** + * Represents a SPDY stream + */ +struct SPDYF_Stream +{ + /** + * This is a doubly-linked list. + */ + struct SPDYF_Stream *next; + + /** + * This is a doubly-linked list. + */ + struct SPDYF_Stream *prev; + + /** + * Reference to the SPDY_Session struct. + */ + struct SPDY_Session *session; + + /** + * Name value pairs, sent within the frame which created the stream. + */ + struct SPDY_NameValue *headers; + + /** + * This stream's ID. + */ + uint32_t stream_id; + + /** + * Stream to which this one is associated. + */ + uint32_t assoc_stream_id; + + /** + * Stream priority. 0 is the highest, 7 is the lowest. + */ + uint8_t priority; + + /** + * Integer specifying the index in the server's CREDENTIAL vector of + * the client certificate to be used for this request The value 0 + * means no client certificate should be associated with this stream. + */ + uint8_t slot; + + /** + * If initially the stream was created as unidirectional. + */ + bool flag_unidirectional; + + /** + * If the stream won't be used for receiving frames anymore. The + * client has sent FLAG_FIN or the stream was terminated with + * RST_STREAM. + */ + bool is_in_closed; + + /** + * If the stream won't be used for sending out frames anymore. The + * server has sent FLAG_FIN or the stream was terminated with + * RST_STREAM. + */ + bool is_out_closed; + + /** + * Which entity (server/client) has created the stream. + */ + bool is_server_initiator; +}; + + +/** + * Represents a SPDY session which is just a TCP connection + */ +struct SPDY_Session +{ + /** + * zlib stream for decompressing all the name/pair values from the + * received frames. All the received compressed data must be + * decompressed within one context: this stream. Thus, it should be + * unique for the session and initialized at its creation. + */ + z_stream zlib_recv_stream; + + /** + * zlib stream for compressing all the name/pair values from the + * frames to be sent. All the sent compressed data must be + * compressed within one context: this stream. Thus, it should be + * unique for the session and initialized at its creation. + */ + z_stream zlib_send_stream; + + /** + * This is a doubly-linked list. + */ + struct SPDY_Session *next; + + /** + * This is a doubly-linked list. + */ + struct SPDY_Session *prev; + + /** + * Reference to the SPDY_Daemon struct. + */ + struct SPDY_Daemon *daemon; + + /** + * Foreign address (of length addr_len). + */ + struct sockaddr *addr; + + /** + * Head of doubly-linked list of the SPDY streams belonging to the + * session. + */ + struct SPDYF_Stream *streams_head; + + /** + * Tail of doubly-linked list of the streams. + */ + struct SPDYF_Stream *streams_tail; + + /** + * Unique TLS context for the session. Initialized on each creation + * (actually when the TCP connection is established). + */ + SPDYF_TLS_SESSION_CONTEXT *tls_context; + + /** + * Head of doubly-linked list of the responses. + */ + struct SPDYF_Response_Queue *response_queue_head; + + /** + * Tail of doubly-linked list of the responses. + */ + struct SPDYF_Response_Queue *response_queue_tail; + + /** + * Buffer for reading requests. + */ + void *read_buffer; + + /** + * Buffer for writing responses. + */ + void *write_buffer; + + /** + * Specific handler for the frame that is currently being received. + */ + void (*frame_handler) (struct SPDY_Session * session); + + /** + * Closure for frame_handler. + */ + void *frame_handler_cls; + + /** + * Extra field to be used by the user with set/get func for whatever + * purpose he wants. + */ + void *user_cls; + + /** + * Number of bytes that the lib must ignore immediately after they + * are read from the TLS socket without adding them to the read buf. + * This is needed, for instance, when receiving frame bigger than + * the buffer to avoid deadlock situations. + */ + size_t read_ignore_bytes; + + /** + * Size of read_buffer (in bytes). This value indicates + * how many bytes we're willing to read into the buffer; + * the real buffer is one byte longer to allow for + * adding zero-termination (when needed). + */ + size_t read_buffer_size; + + /** + * Position where we currently append data in + * read_buffer (last valid position). + */ + size_t read_buffer_offset; + + /** + * Position until where everything was already read + */ + size_t read_buffer_beginning; + + /** + * Size of write_buffer (in bytes). This value indicates + * how many bytes we're willing to prepare for writing. + */ + size_t write_buffer_size; + + /** + * Position where we currently append data in + * write_buffer (last valid position). + */ + size_t write_buffer_offset; + + /** + * Position until where everything was already written to the socket + */ + size_t write_buffer_beginning; + + /** + * Last time this connection had any activity + * (reading or writing). + */ + time_t last_activity; + + /** + * Socket for this connection. Set to -1 if + * this connection has died (daemon should clean + * up in that case). + */ + int socket_fd; + + /** + * Length of the foreign address. + */ + socklen_t addr_len; + + /** + * The biggest stream ID for this session for streams initiated + * by the client. + */ + uint32_t last_in_stream_id; + + /** + * The biggest stream ID for this session for streams initiated + * by the server. + */ + uint32_t last_out_stream_id; + + /** + * This value is updated whenever SYN_REPLY or RST_STREAM are sent + * and is used later in GOAWAY frame. + * TODO it is not clear in the draft what happens when streams are + * not answered in the order of their IDs. Moreover, why should we + * send GOAWAY with the ID of received bogus SYN_STREAM with huge ID? + */ + uint32_t last_replied_to_stream_id; + + /** + * Shows the stream id of the currently handled frame. This value is + * to be used when sending RST_STREAM in answer to a problematic + * frame, e.g. larger than supported. + */ + uint32_t current_stream_id; + + /** + * Shows the current receiving state the session, i.e. what is + * expected to come now, and how it shold be handled. + */ + enum SPDY_SESSION_STATUS status; + + /** + * Has this socket been closed for reading (i.e. + * other side closed the connection)? If so, + * we must completely close the connection once + * we are done sending our response (and stop + * trying to read from this socket). + */ + bool read_closed; + + /** + * If the server sends GOAWAY, it must ignore all SYN_STREAMS for + * this session. Normally the server will soon close the TCP session. + */ + bool is_goaway_sent; + + /** + * If the server receives GOAWAY, it must not send new SYN_STREAMS + * on this session. Normally the client will soon close the TCP + * session. + */ + bool is_goaway_received; +}; + + +/** + * State and settings kept for each SPDY daemon. + */ +struct SPDY_Daemon +{ + + /** + * Tail of doubly-linked list of our current, active sessions. + */ + struct SPDY_Session *sessions_head; + + /** + * Tail of doubly-linked list of our current, active sessions. + */ + struct SPDY_Session *sessions_tail; + + /** + * Tail of doubly-linked list of connections to clean up. + */ + struct SPDY_Session *cleanup_head; + + /** + * Tail of doubly-linked list of connections to clean up. + */ + struct SPDY_Session *cleanup_tail; + + /** + * Unique TLS context for the daemon. Initialized on daemon start. + */ + SPDYF_TLS_DAEMON_CONTEXT *tls_context; + + /** + * Certificate file of the server. File path is kept here. + */ + char *certfile; + + /** + * Key file for the certificate of the server. File path is + * kept here. + */ + char *keyfile; + + + /** + * The address to which the listening socket is bound. + */ + struct sockaddr *address; + + /** + * Callback called when a new SPDY session is + * established by a client + */ + SPDY_NewSessionCallback new_session_cb; + + /** + * Callback called when a client closes the session + */ + SPDY_SessionClosedCallback session_closed_cb; + + /** + * Callback called when a client sends request + */ + SPDY_NewRequestCallback new_request_cb; + + /** + * Callback called when HTTP POST params are received + * after request + */ + SPDY_NewPOSTDataCallback new_post_data_cb; + + /** + * Closure argument for all the callbacks that can be used by the client. + */ + void *cls; + + /** + * Callback called when new stream is created. + */ + SPDYF_NewStreamCallback fnew_stream_cb; + + /** + * Closure argument for all the callbacks defined in the framing layer. + */ + void *fcls; + + /** + * After how many seconds of inactivity should + * connections time out? Zero for no timeout. + */ + time_t session_timeout; + + /** + * Listen socket. + */ + int socket_fd; + + /** + * Daemon's options. + */ + enum SPDY_DAEMON_OPTION options; + + /** + * Daemon's flags. + */ + enum SPDY_DAEMON_FLAG flags; + + /** + * Listen port. + */ + uint16_t port; +}; + + +/** + * Represents a SPDY response. + */ +struct SPDY_Response +{ + /** + * Raw uncompressed stream of the name/value pairs in SPDY frame + * used for the HTTP headers. + */ + void *headers; + + /** + * Raw stream of the data to be sent. Equivalent to the body in HTTP + * response. + */ + void *data; + + /** + * Callback function to be used when the response data is provided + * with callbacks. In this case data must be NULL and data_size must + * be 0. + */ + SPDY_ResponseCallback rcb; + + /** + * Extra argument to rcb. + */ + void *rcb_cls; + + /** + * Length of headers. + */ + size_t headers_size; + + /** + * Length of data. + */ + size_t data_size; + + /** + * The callback func will be called to get that amount of bytes to + * put them into a DATA frame. It is either user preffered or + * the maximum supported by the lib value. + */ + uint32_t rcb_block_size; +}; + + +/* Macros for handling data and structures */ + + +/** + * Insert an element at the head of a DLL. Assumes that head, tail and + * element are structs with prev and next fields. + * + * @param head pointer to the head of the DLL (struct ? *) + * @param tail pointer to the tail of the DLL (struct ? *) + * @param element element to insert (struct ? *) + */ +#define DLL_insert(head,tail,element) do { \ + (element)->next = (head); \ + (element)->prev = NULL; \ + if ((tail) == NULL) \ + (tail) = element; \ + else \ + (head)->prev = element; \ + (head) = (element); } while (0) + + +/** + * Remove an element from a DLL. Assumes + * that head, tail and element are structs + * with prev and next fields. + * + * @param head pointer to the head of the DLL (struct ? *) + * @param tail pointer to the tail of the DLL (struct ? *) + * @param element element to remove (struct ? *) + */ +#define DLL_remove(head,tail,element) do { \ + if ((element)->prev == NULL) \ + (head) = (element)->next; \ + else \ + (element)->prev->next = (element)->next; \ + if ((element)->next == NULL) \ + (tail) = (element)->prev; \ + else \ + (element)->next->prev = (element)->prev; \ + (element)->next = NULL; \ + (element)->prev = NULL; } while (0) + + +/** + * Convert all integers in a SPDY control frame headers structure from + * host byte order to network byte order. + * + * @param frame input and output structure (struct SPDY_Control_Frame *) + */ +#if HAVE_BIG_ENDIAN +#define SPDYF_CONTROL_FRAME_HTON(frame) +#else +#define SPDYF_CONTROL_FRAME_HTON(frame) do { \ + (*((uint16_t *) frame )) = (*((uint8_t *) (frame) +1 )) | ((*((uint8_t *) frame ))<<8);\ + (frame)->type = htons((frame)->type); \ + (frame)->length = HTON24((frame)->length); \ + } while (0) +#endif + + +/** + * Convert all integers in a SPDY control frame headers structure from + * network byte order to host byte order. + * + * @param frame input and output structure (struct SPDY_Control_Frame *) + */ +#if HAVE_BIG_ENDIAN +#define SPDYF_CONTROL_FRAME_NTOH(frame) +#else +#define SPDYF_CONTROL_FRAME_NTOH(frame) do { \ + (*((uint16_t *) frame )) = (*((uint8_t *) (frame) +1 )) | ((*((uint8_t *) frame ))<<8);\ + (frame)->type = ntohs((frame)->type); \ + (frame)->length = NTOH24((frame)->length); \ + } while (0) +#endif + + +/** + * Convert all integers in a SPDY data frame headers structure from + * host byte order to network byte order. + * + * @param frame input and output structure (struct SPDY_Data_Frame *) + */ +#if HAVE_BIG_ENDIAN +#define SPDYF_DATA_FRAME_HTON(frame) +#else +#define SPDYF_DATA_FRAME_HTON(frame) do { \ + *((uint32_t *) frame ) = htonl(*((uint32_t *) frame ));\ + (frame)->length = HTON24((frame)->length); \ + } while (0) +#endif + + +/** + * Convert all integers in a SPDY data frame headers structure from + * network byte order to host byte order. + * + * @param frame input and output structure (struct SPDY_Data_Frame *) + */ +#if HAVE_BIG_ENDIAN +#define SPDYF_DATA_FRAME_NTOH(frame) +#else +#define SPDYF_DATA_FRAME_NTOH(frame) do { \ + *((uint32_t *) frame ) = ntohl(*((uint32_t *) frame ));\ + (frame)->length = NTOH24((frame)->length); \ + } while (0) +#endif + + +/** + * Creates one or more new SPDYF_Response_Queue object to be put on the + * response queue. + * + * @param is_data whether new data frame or new control frame will be + * crerated + * @param data the row stream which will be used as the body of the frame + * @param data_size length of data + * @param response object, part of which is the frame + * @param stream on which data is to be sent + * @param closestream TRUE if the frame must close the stream (with flag) + * @param frqcb callback to notify application layer when the frame + * has been sent or discarded + * @param frqcb_cls closure for frqcb + * @param rrcb callback used by the application layer to notify the + * application when the frame has been sent or discarded. + * frqcb will call it + * @param rrcb_cls closure for rrcb + * @return double linked list of SPDYF_Response_Queue structures: one or + * more frames are returned based on the size of the data + */ +struct SPDYF_Response_Queue * +SPDYF_response_queue_create(bool is_data, + void *data, + size_t data_size, + struct SPDY_Response *response, + struct SPDYF_Stream *stream, + bool closestream, + SPDYF_ResponseQueueResultCallback frqcb, + void *frqcb_cls, + SPDY_ResponseResultCallback rrcb, + void *rrcb_cls); + + +/** + * Destroys SPDYF_Response_Queue structure and whatever is in it. + * + * @param response_queue to destroy + */ +void +SPDYF_response_queue_destroy(struct SPDYF_Response_Queue *response_queue); + + +/** + * Transforms raw binary decomressed stream of headers + * into SPDY_NameValue, containing all of the headers and values. + * + * @param stream that is to be transformed + * @param size length of the stream + * @param container will contain the newly created SPDY_NameValue + * container. Should point to NULL. + * @return SPDY_YES on success + * SPDY_NO on memory error + * SPDY_INPUT_ERROR if the provided stream is not valid + */ +int +SPDYF_name_value_from_stream(void *stream, + size_t size, + struct SPDY_NameValue ** container); + + +/** + * Transforms array of objects of name/values tuples, containing HTTP + * headers, into raw binary stream. The resulting stream is ready to + * be compressed and sent. + * + * @param container one or more SPDY_NameValue objects. Each object + * contains multiple number of name/value tuples. + * @param num_containers length of the array + * @param stream will contain the resulting stream. Should point to NULL. + * @return length of stream or value less than 0 indicating error + */ +ssize_t +SPDYF_name_value_to_stream(struct SPDY_NameValue * container[], + int num_containers, + void **stream); + +#endif diff --git a/src/microspdy/tls.c b/src/microspdy/tls.c @@ -0,0 +1,255 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file tls.c + * @brief TLS handling using libssl. The current code assumes that + * blocking I/O is in use. + * @author Andrey Uzunov + */ + +#include "platform.h" +#include "internal.h" +#include "session.h" +#include "tls.h" + + +/** + * Callback to advertise spdy ver. 3 in Next Protocol Negotiation + * + * @param ssl openssl context for a connection + * @param out must be set to the raw data that is advertised in NPN + * @param outlen must be set to size of out + * @param arg + * @return SSL_TLSEXT_ERR_OK to do advertising + */ +static int +spdyf_next_protos_advertised_cb (SSL *ssl, const unsigned char **out, unsigned int *outlen, void *arg) +{ + (void)ssl; + (void)arg; + static unsigned char npn_spdy3[] = {0x06, // length of "spdy/3" + 0x73,0x70,0x64,0x79,0x2f,0x33};// spdy/3 + + *out = npn_spdy3; + *outlen = 7; // total length of npn_spdy3 + return SSL_TLSEXT_ERR_OK; +} + + +void +SPDYF_tls_global_init() +{ + //error strings are now not used by the lib + //SSL_load_error_strings(); + //init libssl + SSL_library_init(); //always returns 1 + //the table for looking up algos is not used now by the lib + //OpenSSL_add_all_algorithms(); +} + + +void +SPDYF_tls_global_deinit() +{ + //if SSL_load_error_strings was called + //ERR_free_strings(); + //if OpenSSL_add_all_algorithms was called + //EVP_cleanup(); +} + + +int +SPDYF_tls_init(struct SPDY_Daemon *daemon) +{ + //create ssl context. TLSv1 used + if(NULL == (daemon->tls_context = SSL_CTX_new(TLSv1_server_method()))) + { + SPDYF_DEBUG("Couldn't create ssl context"); + return SPDY_NO; + } + //set options for tls + //TODO DH is not enabled for easier debugging + //SSL_CTX_set_options(daemon->tls_context, SSL_OP_SINGLE_DH_USE); + + //TODO here session tickets are disabled for easier debuging with + //wireshark when using Chrome + //SSL_OP_NO_COMPRESSION disables TLS compression to avoid CRIME attack + SSL_CTX_set_options(daemon->tls_context, SSL_OP_NO_TICKET | SSL_OP_NO_COMPRESSION); + if(1 != SSL_CTX_use_certificate_file(daemon->tls_context, daemon->certfile , SSL_FILETYPE_PEM)) + { + SPDYF_DEBUG("Couldn't load the cert file"); + SSL_CTX_free(daemon->tls_context); + return SPDY_NO; + } + if(1 != SSL_CTX_use_PrivateKey_file(daemon->tls_context, daemon->keyfile, SSL_FILETYPE_PEM)) + { + SPDYF_DEBUG("Couldn't load the name file"); + SSL_CTX_free(daemon->tls_context); + return SPDY_NO; + } + SSL_CTX_set_next_protos_advertised_cb(daemon->tls_context, &spdyf_next_protos_advertised_cb, NULL); + //TODO only RC4-SHA is used to make it easy to debug with wireshark + if (1 != SSL_CTX_set_cipher_list(daemon->tls_context, "RC4-SHA")) + { + SPDYF_DEBUG("Couldn't set the desired cipher list"); + SSL_CTX_free(daemon->tls_context); + return SPDY_NO; + } + + return SPDY_YES; +} + + +void +SPDYF_tls_deinit(struct SPDY_Daemon *daemon) +{ + SSL_CTX_free(daemon->tls_context); +} + + +int +SPDYF_tls_new_session(struct SPDY_Session *session) +{ + int ret; + + if(NULL == (session->tls_context = SSL_new(session->daemon->tls_context))) + { + SPDYF_DEBUG("Couldn't create ssl structure"); + return SPDY_NO; + } + if(1 != (ret = SSL_set_fd(session->tls_context, session->socket_fd))) + { + SPDYF_DEBUG("SSL_set_fd %i",ret); + SSL_free(session->tls_context); + session->tls_context = NULL; + return SPDY_NO; + } + + //for non-blocking I/O SSL_accept may return -1 + //and this function won't work + if(1 != (ret = SSL_accept(session->tls_context))) + { + SPDYF_DEBUG("SSL_accept %i",ret); + SSL_free(session->tls_context); + session->tls_context = NULL; + return SPDY_NO; + } + /* alternatively + SSL_set_accept_state(session->tls_context); + * may be called and then the negotiation will be done on reading + */ + + return SPDY_YES; +} + + +void +SPDYF_tls_close_session(struct SPDY_Session *session) +{ + //SSL_shutdown sends TLS "close notify" as in TLS standard. + //The function may fail as it waits for the other party to also close + //the TLS session. The lib just sends it and will close the socket + //after that because the browsers don't seem to care much about + //"close notify" + SSL_shutdown(session->tls_context); + + SSL_free(session->tls_context); +} + + +int +SPDYF_tls_recv(struct SPDY_Session *session, + void * buffer, + size_t size) +{ + int ret; + int n = SSL_read(session->tls_context, + buffer, + size); + //if(n > 0) SPDYF_DEBUG("recvd: %i",n); + if (n <= 0) + { + ret = SSL_get_error(session->tls_context, n); + switch(ret) + { + case SSL_ERROR_ZERO_RETURN: + return 0; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return SPDY_TLS_ERROR_AGAIN; + + case SSL_ERROR_SYSCALL: + if(EINTR == errno) + return SPDY_TLS_ERROR_AGAIN; + + default: + return SPDY_TLS_ERROR_ERROR; + } + } + + return n; +} + + +int +SPDYF_tls_send(struct SPDY_Session *session, + const void * buffer, + size_t size) +{ + int ret; + + int n = SSL_write(session->tls_context, + buffer, + size); + //if(n > 0) SPDYF_DEBUG("sent: %i",n); + if (n <= 0) + { + ret = SSL_get_error(session->tls_context, n); + switch(ret) + { + case SSL_ERROR_ZERO_RETURN: + return 0; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return SPDY_TLS_ERROR_AGAIN; + + case SSL_ERROR_SYSCALL: + if(EINTR == errno) + return SPDY_TLS_ERROR_AGAIN; + + default: + return SPDY_TLS_ERROR_ERROR; + } + } + + return n; +} + + +int +SPDYF_tls_is_pending(struct SPDY_Session *session) +{ + /* From openssl docs: + * BUGS +SSL_pending() takes into account only bytes from the TLS/SSL record that is currently being processed (if any). If the SSL object's read_ahead flag is set, additional protocol bytes may have been read containing more TLS/SSL records; these are ignored by SSL_pending(). + */ + return SSL_pending(session->tls_context) > 0 ? SPDY_YES : SPDY_NO; +} diff --git a/src/microspdy/tls.h b/src/microspdy/tls.h @@ -0,0 +1,171 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2012 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file tls.h + * @brief TLS handling. openssl with NPN is used, but as long as the + * functions conform to this interface file, other libraries + * can be used. + * @author Andrey Uzunov + */ + +#ifndef TLS_H +#define TLS_H + +#include "platform.h" +#include <openssl/err.h> +#include <openssl/ssl.h> +#include <openssl/rand.h> + +/* macros used in other files instead of types. + * useful in case of changing openssl to something else */ +#define SPDYF_TLS_SESSION_CONTEXT SSL +#define SPDYF_TLS_DAEMON_CONTEXT SSL_CTX + + +/** + * Used for return code when reading and writing to the TLS socket. + */ +enum SPDY_TLS_ERROR +{ + /** + * The connection was closed by the other party. + */ + SPDY_TLS_ERROR_CLOSED = 0, + + /** + * Any kind of error ocurred. The session has to be closed. + */ + SPDY_TLS_ERROR_ERROR = -2, + + /** + * The function had to return without processing any data. The whole + * cycle of events has to be called again (SPDY_run) as something + * either has to be written or read or the the syscall was + * interrupted by a signal. + */ + SPDY_TLS_ERROR_AGAIN = -3, +}; + + +/** + * Global initializing of openssl. Must be called only once in the program. + * + */ +void +SPDYF_tls_global_init(); + + +/** + * Global deinitializing of openssl for the whole program. Should be called + * at the end of the program. + * + */ +void +SPDYF_tls_global_deinit(); + + +/** + * Initializing of openssl for a specific daemon. + * Must be called when the daemon starts. + * + * @param daemon SPDY_Daemon for which openssl will be used. Daemon's + * certificate and key file are used. + * @return SPDY_YES on success or SPDY_NO on error + */ +int +SPDYF_tls_init(struct SPDY_Daemon *daemon); + + +/** + * Deinitializing openssl for a daemon. Should be called + * when the deamon is stopped. + * + * @param daemon SPDY_Daemon which is being stopped + */ +void +SPDYF_tls_deinit(struct SPDY_Daemon *daemon); + + +/** + * Initializing openssl for a specific connection. Must be called + * after the connection has been accepted. + * + * @param session SPDY_Session whose socket will be used by openssl + * @return SPDY_NO if some openssl funcs fail. SPDY_YES otherwise + */ +int +SPDYF_tls_new_session(struct SPDY_Session *session); + + +/** + * Deinitializing openssl for a specific connection. Should be called + * closing session's socket. + * + * @param session SPDY_Session whose socket is used by openssl + */ +void +SPDYF_tls_close_session(struct SPDY_Session *session); + + +/** + * Reading from a TLS socket. Reads available data and put it to the + * buffer. + * + * @param session for which data is received + * @param buffer where data from the socket will be written to + * @param size of the buffer + * @return number of bytes (at most size) read from the TLS connection + * 0 if the other party has closed the connection + * SPDY_TLS_ERROR code on error + */ +int +SPDYF_tls_recv(struct SPDY_Session *session, + void * buffer, + size_t size); + + +/** + * Writing to a TLS socket. Writes the data given into the buffer to the + * TLS socket. + * + * @param session whose context is used + * @param buffer from where data will be written to the socket + * @param size number of bytes to be taken from the buffer + * @return number of bytes (at most size) from the buffer that has been + * written to the TLS connection + * 0 if the other party has closed the connection + * SPDY_TLS_ERROR code on error + */ +int +SPDYF_tls_send(struct SPDY_Session *session, + const void * buffer, + size_t size); + + +/** + * Checks if there is data staying in the buffers of the underlying + * system that waits to be read. + * + * @param session which is checked + * @return SPDY_YES if data is pending or SPDY_NO otherwise + */ +int +SPDYF_tls_is_pending(struct SPDY_Session *session); + +#endif diff --git a/src/spdy2http/Makefile.am b/src/spdy2http/Makefile.am @@ -0,0 +1,35 @@ +SUBDIRS = . + +AM_CFLAGS = -DDATADIR=\"$(top_srcdir)/src/datadir/\" + +if USE_COVERAGE + AM_CFLAGS += -fprofile-arcs -ftest-coverage +endif + +if USE_PRIVATE_PLIBC_H + PLIBC_INCLUDE = -I$(top_srcdir)/src/include/plibc +endif + +AM_CPPFLAGS = \ + $(PLIBC_INCLUDE) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/src/include \ + -I$(top_srcdir)/src/applicationlayer \ +$(LIBCURL_CPPFLAGS) + +if !HAVE_W32 +PERF_GET_CONCURRENT=perf_get_concurrent +endif + +bin_PROGRAMS = \ + microspdy2http + +microspdy2http_SOURCES = \ + proxy.c +microspdy2http_LDADD = \ + $(top_builddir)/src/microspdy/libmicrospdy.la \ + -lssl \ + -lcrypto \ + -lz \ + -ldl \ + -lcurl diff --git a/src/spdy2http/proxy.c b/src/spdy2http/proxy.c @@ -0,0 +1,625 @@ +/* + This file is part of libmicrospdy + Copyright (C) 2013 Andrey Uzunov + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file proxy.c + * @brief Translates incoming SPDY requests to http server on localhost. + * Uses libcurl. + * @author Andrey Uzunov + */ + +#include "platform.h" +#include <unistd.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include "microspdy.h" +#include <curl/curl.h> +#include <assert.h> + + +#define PRINT_INFO(msg) do{\ + printf("%i:%s\n", __LINE__, msg);\ + fflush(stdout);\ + }\ + while(0) + + +#define PRINT_INFO2(fmt, ...) do{\ + printf("%i\n", __LINE__);\ + printf(fmt,##__VA_ARGS__);\ + printf("\n");\ + fflush(stdout);\ + }\ + while(0) + + +#define CURL_SETOPT(handle, opt, val) do{\ + int ret; \ + if(CURLE_OK != (ret = curl_easy_setopt(handle, opt, val))) \ + { \ + PRINT_INFO2("curl_easy_setopt failed (%i = %i)", opt, ret); \ + abort(); \ + } \ + }\ + while(0) + + +int run = 1; +char* http_host; +CURLM *multi_handle; +int still_running = 0; /* keep number of running handles */ +int http10=0; + +struct Proxy +{ + char *path; + struct SPDY_Request *request; + struct SPDY_Response *response; + CURL *curl_handle; + struct curl_slist *curl_headers; + struct SPDY_NameValue *headers; + char *version; + char *status_msg; + void *http_body; + size_t http_body_size; + ssize_t length; + int status; +}; + + +ssize_t +response_callback (void *cls, + void *buffer, + size_t max, + bool *more) +{ + int ret; + struct Proxy *proxy = (struct Proxy *)cls; + void *newbody; + + //printf("response_callback\n"); + + assert(0 != proxy->length); + + *more = true; + if(!proxy->http_body_size)//nothing to write now + return 0; + + if(max >= proxy->http_body_size) + { + ret = proxy->http_body_size; + newbody = NULL; + } + else + { + ret = max; + if(NULL == (newbody = malloc(proxy->http_body_size - max))) + { + PRINT_INFO("no memory"); + return -1; + } + memcpy(newbody, proxy->http_body + max, proxy->http_body_size - max); + } + memcpy(buffer, proxy->http_body, ret); + free(proxy->http_body); + proxy->http_body = newbody; + proxy->http_body_size -= ret; + + if(proxy->length >= 0) + { + proxy->length -= ret; + //printf("pr len %i", proxy->length); + if(proxy->length <= 0) + { + *more = false; + //last frame + proxy->length = 0; + } + } + + return ret; +} + + +void +response_done_callback(void *cls, + struct SPDY_Response *response, + struct SPDY_Request *request, + enum SPDY_RESPONSE_RESULT status, + bool streamopened) +{ + (void)streamopened; + struct Proxy *proxy = (struct Proxy *)cls; + int ret; + + //printf("response_done_callback\n"); + + //printf("answer for %s was sent\n", (char *)cls); + + if(SPDY_RESPONSE_RESULT_SUCCESS != status) + { + printf("answer was NOT sent, %i\n",status); + } + if(CURLM_OK != (ret = curl_multi_remove_handle(multi_handle, proxy->curl_handle))) + { + PRINT_INFO2("curl_multi_remove_handle failed (%i)", ret); + } + curl_slist_free_all(proxy->curl_headers); + curl_easy_cleanup(proxy->curl_handle); + + SPDY_destroy_request(request); + SPDY_destroy_response(response); + if(!strcmp("/close",proxy->path)) run = 0; + free(proxy->path); + free(proxy); +} + + +static size_t +curl_header_cb(void *ptr, size_t size, size_t nmemb, void *userp) +{ + size_t realsize = size * nmemb; + struct Proxy *proxy = (struct Proxy *)userp; + char *line = (char *)ptr; + char *name; + char *value; + char *status; + const char *const*length; + int i; + int pos; + + //printf("curl_header_cb\n"); + + if('\r' == line[0]) + { + //all headers were already handled; prepare spdy frames + if(NULL == (proxy->response = SPDY_build_response_with_callback(proxy->status, + proxy->status_msg, + proxy->version, + proxy->headers, + &response_callback, + proxy, + 0))) + { + PRINT_INFO("no response"); + abort(); + } + if(NULL != (length = SPDY_name_value_lookup(proxy->headers, + SPDY_HTTP_HEADER_CONTENT_LENGTH, + &i))) + proxy->length = atoi(length[0]); + else + proxy->length = -1; + SPDY_name_value_destroy(proxy->headers); + free(proxy->status_msg); + free(proxy->version); + + if(SPDY_YES != SPDY_queue_response(proxy->request, + proxy->response, + true, + false, + &response_done_callback, + proxy)) + { + PRINT_INFO("no queue"); + abort(); + } + //printf("spdy headers queued %i\n"); + + return realsize; + } + + pos = 0; + if(NULL == proxy->version) + { + //first line from headers + //version + for(i=pos; i<realsize && ' '!=line[i]; ++i); + if(i == realsize) + { + PRINT_INFO("error on parsing headers"); + abort(); + } + if(NULL == (proxy->version = strndup(line, i - pos))) + { + PRINT_INFO("no memory"); + abort(); + } + pos = i+1; + + //status (number) + for(i=pos; i<realsize && ' '!=line[i] && '\r'!=line[i]; ++i); + if(NULL == (status = strndup(&(line[pos]), i - pos))) + { + PRINT_INFO("no memory"); + abort(); + } + proxy->status = atoi(status); + free(status); + if(i<realsize && '\r'!=line[i]) + { + //status (message) + pos = i+1; + for(i=pos; i<realsize && '\r'!=line[i]; ++i); + if(NULL == (proxy->status_msg = strndup(&(line[pos]), i - pos))) + { + PRINT_INFO("no memory"); + abort(); + } + } + return realsize; + } + + //other lines + //header name + for(i=pos; i<realsize && ':'!=line[i] && '\r'!=line[i]; ++i) + line[i] = tolower(line[i]); //spdy requires lower case + if(NULL == (name = strndup(line, i - pos))) + { + PRINT_INFO("no memory"); + abort(); + } + if(0 == strcmp(SPDY_HTTP_HEADER_CONNECTION, name) + || 0 == strcmp(SPDY_HTTP_HEADER_KEEP_ALIVE, name)) + { + //forbidden in spdy, ignore + free(name); + return realsize; + } + if(i == realsize || '\r'==line[i]) + { + //no value. is it possible? + if(SPDY_YES != SPDY_name_value_add(proxy->headers, name, "")) + { + PRINT_INFO("SPDY_name_value_add failed"); + abort(); + } + return realsize; + } + + //header value + pos = i+1; + while(pos<realsize && isspace(line[pos])) ++pos; //remove leading space + for(i=pos; i<realsize && '\r'!=line[i]; ++i); + if(NULL == (value = strndup(&(line[pos]), i - pos))) + { + PRINT_INFO("no memory"); + abort(); + } + if(SPDY_YES != SPDY_name_value_add(proxy->headers, name, value)) + { + PRINT_INFO("SPDY_name_value_add failed"); + abort(); + } + free(name); + free(value); + + return realsize; +} + + +static size_t +curl_write_cb(void *contents, size_t size, size_t nmemb, void *userp) +{ + size_t realsize = size * nmemb; + struct Proxy *proxy = (struct Proxy *)userp; + + //printf("curl_write_cb %i\n", realsize); + + if(NULL == proxy->http_body) + proxy->http_body = malloc(realsize); + else + proxy->http_body = realloc(proxy->http_body, proxy->http_body_size + realsize); + if(NULL == proxy->http_body) + { + PRINT_INFO("not enough memory (realloc returned NULL)"); + return 0; + } + + memcpy(proxy->http_body + proxy->http_body_size, contents, realsize); + proxy->http_body_size += realsize; + + return realsize; +} + + +int +iterate_cb (void *cls, const char *name, const char * const * value, int num_values) +{ + struct Proxy *proxy = (struct Proxy *)cls; + struct curl_slist **curl_headers = (&(proxy->curl_headers)); + char *line; + int line_len = strlen(name) + 3; //+ ": \0" + int i; + + for(i=0; i<num_values; ++i) + { + if(i) line_len += 2; //", " + line_len += strlen(value[i]); + } + + if(NULL == (line = malloc(line_len))) + { + //no recovory + PRINT_INFO("no memory"); + abort(); + } + line[0] = 0; + + strcat(line, name); + strcat(line, ": "); + //all spdy header names are lower case; + //for simplicity here we just capitalize the first letter + line[0] = toupper(line[0]); + + for(i=0; i<num_values; ++i) + { + if(i) strcat(line, ", "); + strcat(line, value[i]); + } + if(NULL == (*curl_headers = curl_slist_append(*curl_headers, line))) + { + PRINT_INFO("curl_slist_append failed"); + abort(); + } + free(line); + + return SPDY_YES; +} + + +void +standard_request_handler(void *cls, + struct SPDY_Request * request, + uint8_t priority, + const char *method, + const char *path, + const char *version, + const char *host, + const char *scheme, + struct SPDY_NameValue * headers) +{ + (void)cls; + (void)priority; + (void)host; + (void)scheme; + + char *url; + struct Proxy *proxy; + int ret; + + //printf("received request for '%s %s %s'\n", method, path, version); + if(NULL == (proxy = malloc(sizeof(struct Proxy)))) + { + PRINT_INFO("No memory"); + abort(); + } + memset(proxy, 0, sizeof(struct Proxy)); + proxy->request = request; + if(NULL == (proxy->headers = SPDY_name_value_create())) + { + PRINT_INFO("No memory"); + abort(); + } + + if(-1 == asprintf(&url,"%s%s%s","http://", http_host, path)) + { + PRINT_INFO("No memory"); + abort(); + } + + if(NULL == (proxy->path = strdup(path))) + { + PRINT_INFO("No memory"); + abort(); + } + + SPDY_name_value_iterate(headers, &iterate_cb, proxy); + + if(NULL == (proxy->curl_handle = curl_easy_init())) + { + PRINT_INFO("curl_easy_init failed"); + abort(); + } + + //CURL_SETOPT(proxy->curl_handle, CURLOPT_VERBOSE, 1); + CURL_SETOPT(proxy->curl_handle, CURLOPT_URL, url); + free(url); + if(http10) + CURL_SETOPT(proxy->curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + CURL_SETOPT(proxy->curl_handle, CURLOPT_WRITEFUNCTION, curl_write_cb); + CURL_SETOPT(proxy->curl_handle, CURLOPT_WRITEDATA, proxy); + CURL_SETOPT(proxy->curl_handle, CURLOPT_HEADERFUNCTION, curl_header_cb); + CURL_SETOPT(proxy->curl_handle, CURLOPT_HEADERDATA, proxy); + CURL_SETOPT(proxy->curl_handle, CURLOPT_HTTPHEADER, proxy->curl_headers); + CURL_SETOPT(proxy->curl_handle, CURLOPT_SSL_VERIFYPEER, 0L); + CURL_SETOPT(proxy->curl_handle, CURLOPT_SSL_VERIFYHOST, 0L); + + if(CURLM_OK != (ret = curl_multi_add_handle(multi_handle, proxy->curl_handle))) + { + PRINT_INFO2("curl_multi_add_handle failed (%i)", ret); + abort(); + } + + if(CURLM_OK != (ret = curl_multi_perform(multi_handle, &still_running)) + && CURLM_CALL_MULTI_PERFORM != ret) + { + PRINT_INFO2("curl_multi_perform failed (%i)", ret); + abort(); + } +} + +int +main (int argc, char *const *argv) +{ + unsigned long long timeoutlong=0; + long curl_timeo = -1; + struct timeval timeout; + int ret; + fd_set rs; + fd_set ws; + fd_set es; + fd_set curl_rs; + fd_set curl_ws; + fd_set curl_es; + int maxfd = -1; + struct SPDY_Daemon *daemon; + + //signal(SIGPIPE, SIG_IGN); + + if(argc != 6) + { + printf("Usage: %s cert-file key-file host port http/1.0(yes/no)\n", argv[0]); + return 1; + } + + SPDY_init(); + + daemon = SPDY_start_daemon(atoi(argv[4]), + argv[1], + argv[2], + NULL, + NULL, + &standard_request_handler, + NULL, + NULL, + SPDY_DAEMON_OPTION_SESSION_TIMEOUT, + 1800, + SPDY_DAEMON_OPTION_END); + + if(NULL==daemon){ + printf("no daemon\n"); + return 1; + } + + multi_handle = curl_multi_init(); + + if(NULL==multi_handle){ + PRINT_INFO("no multi_handle"); + abort(); + } + + if(!strcmp("yes", argv[5])) + http10 = 1; + + http_host = argv[3]; + timeout.tv_usec = 0; + + do + { + //printf("still %i\n", still_running); + FD_ZERO(&rs); + FD_ZERO(&ws); + FD_ZERO(&es); + FD_ZERO(&curl_rs); + FD_ZERO(&curl_ws); + FD_ZERO(&curl_es); + + if(still_running > 0) + timeout.tv_sec = 0; //return immediately + else + { + ret = SPDY_get_timeout(daemon, &timeoutlong); + if(SPDY_NO == ret) + timeout.tv_sec = 1; + else + timeout.tv_sec = timeoutlong; + } + timeout.tv_usec = 0; + + maxfd = SPDY_get_fdset (daemon, + &rs, + &ws, + &es); + assert(-1 != maxfd); + + ret = select(maxfd+1, &rs, &ws, &es, &timeout); + + switch(ret) { + case -1: + PRINT_INFO2("select error: %i", errno); + break; + case 0: + break; + default: + SPDY_run(daemon); + break; + } + + timeout.tv_sec = 0; + if(still_running > 0) + { + if(CURLM_OK != (ret = curl_multi_timeout(multi_handle, &curl_timeo))) + { + PRINT_INFO2("curl_multi_timeout failed (%i)", ret); + abort(); + } + if(curl_timeo >= 0 && curl_timeo < 500) + timeout.tv_usec = curl_timeo * 1000; + else + timeout.tv_usec = 500000; + } + else continue; + //else timeout.tv_usec = 500000; + + if(CURLM_OK != (ret = curl_multi_fdset(multi_handle, &curl_rs, &curl_ws, &curl_es, &maxfd))) + { + PRINT_INFO2("curl_multi_fdset failed (%i)", ret); + abort(); + } + if(-1 == maxfd) + { + PRINT_INFO("maxfd is -1"); + //continue; + ret = 0; + } + else + ret = select(maxfd+1, &curl_rs, &curl_ws, &curl_es, &timeout); + + switch(ret) { + case -1: + PRINT_INFO2("select error: %i", errno); + break; + case 0: /* timeout */ + //break or not + default: /* action */ + if(CURLM_OK != (ret = curl_multi_perform(multi_handle, &still_running)) + && CURLM_CALL_MULTI_PERFORM != ret) + { + PRINT_INFO2("curl_multi_perform failed (%i)", ret); + abort(); + } + break; + } + } + while(run); + + curl_multi_cleanup(multi_handle); + + SPDY_stop_daemon(daemon); + + SPDY_deinit(); + + return 0; +} +